diff --git a/homeassistant/__main__.py b/homeassistant/__main__.py index 80bc1dccf..e858db70d 100644 --- a/homeassistant/__main__.py +++ b/homeassistant/__main__.py @@ -25,6 +25,7 @@ def attempt_use_uvloop() -> None: try: import uvloop + asyncio.set_event_loop_policy(uvloop.EventLoopPolicy()) except ImportError: pass @@ -33,28 +34,40 @@ def attempt_use_uvloop() -> None: def validate_python() -> None: """Validate that the right Python version is running.""" if sys.version_info[:3] < REQUIRED_PYTHON_VER: - print("Home Assistant requires at least Python {}.{}.{}".format( - *REQUIRED_PYTHON_VER)) + print( + "Home Assistant requires at least Python {}.{}.{}".format( + *REQUIRED_PYTHON_VER + ) + ) sys.exit(1) def ensure_config_path(config_dir: str) -> None: """Validate the configuration directory.""" import homeassistant.config as config_util - lib_dir = os.path.join(config_dir, 'deps') + + lib_dir = os.path.join(config_dir, "deps") # Test if configuration directory exists if not os.path.isdir(config_dir): if config_dir != config_util.get_default_config_dir(): - print(('Fatal Error: Specified configuration directory does ' - 'not exist {} ').format(config_dir)) + print( + ( + "Fatal Error: Specified configuration directory does " + "not exist {} " + ).format(config_dir) + ) sys.exit(1) try: os.mkdir(config_dir) except OSError: - print(('Fatal Error: Unable to create default configuration ' - 'directory {} ').format(config_dir)) + print( + ( + "Fatal Error: Unable to create default configuration " + "directory {} " + ).format(config_dir) + ) sys.exit(1) # Test if library directory exists @@ -62,18 +75,22 @@ def ensure_config_path(config_dir: str) -> None: try: os.mkdir(lib_dir) except OSError: - print(('Fatal Error: Unable to create library ' - 'directory {} ').format(lib_dir)) + print( + ("Fatal Error: Unable to create library " "directory {} ").format( + lib_dir + ) + ) sys.exit(1) def ensure_config_file(config_dir: str) -> str: """Ensure configuration file exists.""" import homeassistant.config as config_util + config_path = config_util.ensure_config_exists(config_dir) if config_path is None: - print('Error getting configuration path') + print("Error getting configuration path") sys.exit(1) return config_path @@ -82,71 +99,72 @@ def ensure_config_file(config_dir: str) -> str: def get_arguments() -> argparse.Namespace: """Get parsed passed in arguments.""" import homeassistant.config as config_util + parser = argparse.ArgumentParser( - description="Home Assistant: Observe, Control, Automate.") - parser.add_argument('--version', action='version', version=__version__) + description="Home Assistant: Observe, Control, Automate." + ) + parser.add_argument("--version", action="version", version=__version__) parser.add_argument( - '-c', '--config', - metavar='path_to_config_dir', + "-c", + "--config", + metavar="path_to_config_dir", default=config_util.get_default_config_dir(), - help="Directory that contains the Home Assistant configuration") + help="Directory that contains the Home Assistant configuration", + ) parser.add_argument( - '--demo-mode', - action='store_true', - help='Start Home Assistant in demo mode') + "--demo-mode", action="store_true", help="Start Home Assistant in demo mode" + ) parser.add_argument( - '--debug', - action='store_true', - help='Start Home Assistant in debug mode') + "--debug", action="store_true", help="Start Home Assistant in debug mode" + ) parser.add_argument( - '--open-ui', - action='store_true', - help='Open the webinterface in a browser') + "--open-ui", action="store_true", help="Open the webinterface in a browser" + ) parser.add_argument( - '--skip-pip', - action='store_true', - help='Skips pip install of required packages on startup') + "--skip-pip", + action="store_true", + help="Skips pip install of required packages on startup", + ) parser.add_argument( - '-v', '--verbose', - action='store_true', - help="Enable verbose logging to file.") + "-v", "--verbose", action="store_true", help="Enable verbose logging to file." + ) parser.add_argument( - '--pid-file', - metavar='path_to_pid_file', + "--pid-file", + metavar="path_to_pid_file", default=None, - help='Path to PID file useful for running as daemon') + help="Path to PID file useful for running as daemon", + ) parser.add_argument( - '--log-rotate-days', + "--log-rotate-days", type=int, default=None, - help='Enables daily log rotation and keeps up to the specified days') + help="Enables daily log rotation and keeps up to the specified days", + ) parser.add_argument( - '--log-file', + "--log-file", type=str, default=None, - help='Log file to write to. If not set, CONFIG/home-assistant.log ' - 'is used') + help="Log file to write to. If not set, CONFIG/home-assistant.log " "is used", + ) parser.add_argument( - '--log-no-color', - action='store_true', - help="Disable color logs") + "--log-no-color", action="store_true", help="Disable color logs" + ) parser.add_argument( - '--runner', - action='store_true', - help='On restart exit with code {}'.format(RESTART_EXIT_CODE)) + "--runner", + action="store_true", + help="On restart exit with code {}".format(RESTART_EXIT_CODE), + ) parser.add_argument( - '--script', - nargs=argparse.REMAINDER, - help='Run one of the embedded scripts') + "--script", nargs=argparse.REMAINDER, help="Run one of the embedded scripts" + ) if os.name == "posix": parser.add_argument( - '--daemon', - action='store_true', - help='Run Home Assistant as daemon') + "--daemon", action="store_true", help="Run Home Assistant as daemon" + ) arguments = parser.parse_args() if os.name != "posix" or arguments.debug or arguments.runner: - setattr(arguments, 'daemon', False) + setattr(arguments, "daemon", False) return arguments @@ -167,8 +185,8 @@ def daemonize() -> None: sys.exit(0) # redirect standard file descriptors to devnull - infd = open(os.devnull, 'r') - outfd = open(os.devnull, 'a+') + infd = open(os.devnull, "r") + outfd = open(os.devnull, "a+") sys.stdout.flush() sys.stderr.flush() os.dup2(infd.fileno(), sys.stdin.fileno()) @@ -180,7 +198,7 @@ def check_pid(pid_file: str) -> None: """Check that Home Assistant is not already running.""" # Check pid file try: - with open(pid_file, 'r') as file: + with open(pid_file, "r") as file: pid = int(file.readline()) except IOError: # PID File does not exist @@ -195,7 +213,7 @@ def check_pid(pid_file: str) -> None: except OSError: # PID does not exist return - print('Fatal Error: HomeAssistant is already running.') + print("Fatal Error: HomeAssistant is already running.") sys.exit(1) @@ -203,10 +221,10 @@ def write_pid(pid_file: str) -> None: """Create a PID File.""" pid = os.getpid() try: - with open(pid_file, 'w') as file: + with open(pid_file, "w") as file: file.write(str(pid)) except IOError: - print('Fatal Error: Unable to write pid file {}'.format(pid_file)) + print("Fatal Error: Unable to write pid file {}".format(pid_file)) sys.exit(1) @@ -230,23 +248,21 @@ def closefds_osx(min_fd: int, max_fd: int) -> None: def cmdline() -> List[str]: """Collect path and arguments to re-execute the current hass instance.""" - if os.path.basename(sys.argv[0]) == '__main__.py': + if os.path.basename(sys.argv[0]) == "__main__.py": modulepath = os.path.dirname(sys.argv[0]) - os.environ['PYTHONPATH'] = os.path.dirname(modulepath) - return [sys.executable] + [arg for arg in sys.argv if - arg != '--daemon'] + os.environ["PYTHONPATH"] = os.path.dirname(modulepath) + return [sys.executable] + [arg for arg in sys.argv if arg != "--daemon"] - return [arg for arg in sys.argv if arg != '--daemon'] + return [arg for arg in sys.argv if arg != "--daemon"] -def setup_and_run_hass(config_dir: str, - args: argparse.Namespace) -> int: +def setup_and_run_hass(config_dir: str, args: argparse.Namespace) -> int: """Set up HASS and run.""" from homeassistant import bootstrap # Run a simple daemon runner process on Windows to handle restarts - if os.name == 'nt' and '--runner' not in sys.argv: - nt_args = cmdline() + ['--runner'] + if os.name == "nt" and "--runner" not in sys.argv: + nt_args = cmdline() + ["--runner"] while True: try: subprocess.check_call(nt_args) @@ -256,21 +272,27 @@ def setup_and_run_hass(config_dir: str, sys.exit(exc.returncode) if args.demo_mode: - config = { - 'frontend': {}, - 'demo': {} - } # type: Dict[str, Any] + config = {"frontend": {}, "demo": {}} # type: Dict[str, Any] hass = bootstrap.from_config_dict( - config, config_dir=config_dir, verbose=args.verbose, - skip_pip=args.skip_pip, log_rotate_days=args.log_rotate_days, - log_file=args.log_file, log_no_color=args.log_no_color) + config, + config_dir=config_dir, + verbose=args.verbose, + skip_pip=args.skip_pip, + log_rotate_days=args.log_rotate_days, + log_file=args.log_file, + log_no_color=args.log_no_color, + ) else: config_file = ensure_config_file(config_dir) - print('Config directory:', config_dir) + print("Config directory:", config_dir) hass = bootstrap.from_config_file( - config_file, verbose=args.verbose, skip_pip=args.skip_pip, - log_rotate_days=args.log_rotate_days, log_file=args.log_file, - log_no_color=args.log_no_color) + config_file, + verbose=args.verbose, + skip_pip=args.skip_pip, + log_rotate_days=args.log_rotate_days, + log_file=args.log_file, + log_no_color=args.log_no_color, + ) if hass is None: return -1 @@ -283,12 +305,14 @@ def setup_and_run_hass(config_dir: str, """Open the web interface in a browser.""" if hass.config.api is not None: # type: ignore import webbrowser + webbrowser.open(hass.config.api.base_url) # type: ignore run_callback_threadsafe( hass.loop, hass.bus.async_listen_once, - EVENT_HOMEASSISTANT_START, open_browser + EVENT_HOMEASSISTANT_START, + open_browser, ) return hass.start() @@ -298,17 +322,17 @@ def try_to_restart() -> None: """Attempt to clean up state and start a new Home Assistant instance.""" # Things should be mostly shut down already at this point, now just try # to clean up things that may have been left behind. - sys.stderr.write('Home Assistant attempting to restart.\n') + sys.stderr.write("Home Assistant attempting to restart.\n") # Count remaining threads, ideally there should only be one non-daemonized # thread left (which is us). Nothing we really do with it, but it might be # useful when debugging shutdown/restart issues. try: - nthreads = sum(thread.is_alive() and not thread.daemon - for thread in threading.enumerate()) + nthreads = sum( + thread.is_alive() and not thread.daemon for thread in threading.enumerate() + ) if nthreads > 1: - sys.stderr.write( - "Found {} non-daemonic threads.\n".format(nthreads)) + sys.stderr.write("Found {} non-daemonic threads.\n".format(nthreads)) # Somehow we sometimes seem to trigger an assertion in the python threading # module. It seems we find threads that have no associated OS level thread @@ -322,7 +346,7 @@ def try_to_restart() -> None: except ValueError: max_fd = 256 - if platform.system() == 'Darwin': + if platform.system() == "Darwin": closefds_osx(3, max_fd) else: os.closerange(3, max_fd) @@ -341,7 +365,7 @@ def main() -> int: validate_python() monkey_patch_needed = sys.version_info[:3] < (3, 6, 3) - if monkey_patch_needed and os.environ.get('HASS_NO_MONKEY') != '1': + if monkey_patch_needed and os.environ.get("HASS_NO_MONKEY") != "1": if sys.version_info[:2] >= (3, 6): monkey_patch.disable_c_asyncio() monkey_patch.patch_weakref_tasks() @@ -352,6 +376,7 @@ def main() -> int: if args.script is not None: from homeassistant import scripts + return scripts.run(args.script) config_dir = os.path.join(os.getcwd(), args.config) diff --git a/homeassistant/auth/__init__.py b/homeassistant/auth/__init__.py index c6f978640..420b65c65 100644 --- a/homeassistant/auth/__init__.py +++ b/homeassistant/auth/__init__.py @@ -23,9 +23,10 @@ _ProviderDict = Dict[_ProviderKey, AuthProvider] async def auth_manager_from_config( - hass: HomeAssistant, - provider_configs: List[Dict[str, Any]], - module_configs: List[Dict[str, Any]]) -> 'AuthManager': + hass: HomeAssistant, + provider_configs: List[Dict[str, Any]], + module_configs: List[Dict[str, Any]], +) -> "AuthManager": """Initialize an auth manager from config. CORE_CONFIG_SCHEMA will make sure do duplicated auth providers or @@ -34,8 +35,11 @@ async def auth_manager_from_config( store = auth_store.AuthStore(hass) if provider_configs: providers = await asyncio.gather( - *[auth_provider_from_config(hass, store, config) - for config in provider_configs]) + *[ + auth_provider_from_config(hass, store, config) + for config in provider_configs + ] + ) else: providers = () # So returned auth providers are in same order as config @@ -46,8 +50,8 @@ async def auth_manager_from_config( if module_configs: modules = await asyncio.gather( - *[auth_mfa_module_from_config(hass, config) - for config in module_configs]) + *[auth_mfa_module_from_config(hass, config) for config in module_configs] + ) else: modules = () # So returned auth modules are in same order as config @@ -62,17 +66,21 @@ async def auth_manager_from_config( class AuthManager: """Manage the authentication for Home Assistant.""" - def __init__(self, hass: HomeAssistant, store: auth_store.AuthStore, - providers: _ProviderDict, mfa_modules: _MfaModuleDict) \ - -> None: + def __init__( + self, + hass: HomeAssistant, + store: auth_store.AuthStore, + providers: _ProviderDict, + mfa_modules: _MfaModuleDict, + ) -> None: """Initialize the auth manager.""" self.hass = hass self._store = store self._providers = providers self._mfa_modules = mfa_modules self.login_flow = data_entry_flow.FlowManager( - hass, self._async_create_login_flow, - self._async_finish_login_flow) + hass, self._async_create_login_flow, self._async_finish_login_flow + ) @property def active(self) -> bool: @@ -87,7 +95,7 @@ class AuthManager: Should be removed when we removed legacy_api_password auth providers. """ for provider_type, _ in self._providers: - if provider_type == 'legacy_api_password': + if provider_type == "legacy_api_password": return True return False @@ -101,8 +109,7 @@ class AuthManager: """Return a list of available auth modules.""" return list(self._mfa_modules.values()) - def get_auth_mfa_module(self, module_id: str) \ - -> Optional[MultiFactorAuthModule]: + def get_auth_mfa_module(self, module_id: str) -> Optional[MultiFactorAuthModule]: """Return an multi-factor auth module, None if not found.""" return self._mfa_modules.get(module_id) @@ -115,7 +122,8 @@ class AuthManager: return await self._store.async_get_user(user_id) async def async_get_user_by_credentials( - self, credentials: models.Credentials) -> Optional[models.User]: + self, credentials: models.Credentials + ) -> Optional[models.User]: """Get a user by credential, return None if not found.""" for user in await self.async_get_users(): for creds in user.credentials: @@ -127,49 +135,43 @@ class AuthManager: async def async_create_system_user(self, name: str) -> models.User: """Create a system user.""" return await self._store.async_create_user( - name=name, - system_generated=True, - is_active=True, + name=name, system_generated=True, is_active=True ) async def async_create_user(self, name: str) -> models.User: """Create a user.""" - kwargs = { - 'name': name, - 'is_active': True, - } # type: Dict[str, Any] + kwargs = {"name": name, "is_active": True} # type: Dict[str, Any] if await self._user_should_be_owner(): - kwargs['is_owner'] = True + kwargs["is_owner"] = True return await self._store.async_create_user(**kwargs) - async def async_get_or_create_user(self, credentials: models.Credentials) \ - -> models.User: + async def async_get_or_create_user( + self, credentials: models.Credentials + ) -> models.User: """Get or create a user.""" if not credentials.is_new: user = await self.async_get_user_by_credentials(credentials) if user is None: - raise ValueError('Unable to find the user.') + raise ValueError("Unable to find the user.") else: return user auth_provider = self._async_get_auth_provider(credentials) if auth_provider is None: - raise RuntimeError('Credential with unknown provider encountered') + raise RuntimeError("Credential with unknown provider encountered") - info = await auth_provider.async_user_meta_for_credentials( - credentials) + info = await auth_provider.async_user_meta_for_credentials(credentials) return await self._store.async_create_user( - credentials=credentials, - name=info.name, - is_active=info.is_active, + credentials=credentials, name=info.name, is_active=info.is_active ) - async def async_link_user(self, user: models.User, - credentials: models.Credentials) -> None: + async def async_link_user( + self, user: models.User, credentials: models.Credentials + ) -> None: """Link credentials to an existing user.""" await self._store.async_link_user(user, credentials) @@ -192,47 +194,50 @@ class AuthManager: async def async_deactivate_user(self, user: models.User) -> None: """Deactivate a user.""" if user.is_owner: - raise ValueError('Unable to deactive the owner') + raise ValueError("Unable to deactive the owner") await self._store.async_deactivate_user(user) - async def async_remove_credentials( - self, credentials: models.Credentials) -> None: + async def async_remove_credentials(self, credentials: models.Credentials) -> None: """Remove credentials.""" provider = self._async_get_auth_provider(credentials) - if (provider is not None and - hasattr(provider, 'async_will_remove_credentials')): + if provider is not None and hasattr(provider, "async_will_remove_credentials"): # https://github.com/python/mypy/issues/1424 - await provider.async_will_remove_credentials( # type: ignore - credentials) + await provider.async_will_remove_credentials(credentials) # type: ignore await self._store.async_remove_credentials(credentials) - async def async_enable_user_mfa(self, user: models.User, - mfa_module_id: str, data: Any) -> None: + async def async_enable_user_mfa( + self, user: models.User, mfa_module_id: str, data: Any + ) -> None: """Enable a multi-factor auth module for user.""" if user.system_generated: - raise ValueError('System generated users cannot enable ' - 'multi-factor auth module.') + raise ValueError( + "System generated users cannot enable " "multi-factor auth module." + ) module = self.get_auth_mfa_module(mfa_module_id) if module is None: - raise ValueError('Unable find multi-factor auth module: {}' - .format(mfa_module_id)) + raise ValueError( + "Unable find multi-factor auth module: {}".format(mfa_module_id) + ) await module.async_setup_user(user.id, data) - async def async_disable_user_mfa(self, user: models.User, - mfa_module_id: str) -> None: + async def async_disable_user_mfa( + self, user: models.User, mfa_module_id: str + ) -> None: """Disable a multi-factor auth module for user.""" if user.system_generated: - raise ValueError('System generated users cannot disable ' - 'multi-factor auth module.') + raise ValueError( + "System generated users cannot disable " "multi-factor auth module." + ) module = self.get_auth_mfa_module(mfa_module_id) if module is None: - raise ValueError('Unable find multi-factor auth module: {}' - .format(mfa_module_id)) + raise ValueError( + "Unable find multi-factor auth module: {}".format(mfa_module_id) + ) await module.async_depose_user(user.id) @@ -245,20 +250,23 @@ class AuthManager: return modules 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: + 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: - raise ValueError('User is not active') + raise ValueError("User is not active") if user.system_generated and client_id is not None: raise ValueError( - 'System generated users cannot have refresh tokens connected ' - 'to a client.') + "System generated users cannot have refresh tokens connected " + "to a client." + ) if token_type is None: if user.system_generated: @@ -268,62 +276,77 @@ class AuthManager: if user.system_generated != (token_type == models.TOKEN_TYPE_SYSTEM): raise ValueError( - 'System generated users can only have system type ' - 'refresh tokens') + "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.') + raise ValueError("Client is required to generate a refresh token.") - 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 + 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): + 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)) + 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) + 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]: + self, token_id: str + ) -> Optional[models.RefreshToken]: """Get refresh token by id.""" return await self._store.async_get_refresh_token(token_id) async def async_get_refresh_token_by_token( - self, token: str) -> Optional[models.RefreshToken]: + self, token: str + ) -> Optional[models.RefreshToken]: """Get refresh token by token.""" return await self._store.async_get_refresh_token_by_token(token) - async def async_remove_refresh_token(self, - refresh_token: models.RefreshToken) \ - -> None: + async def async_remove_refresh_token( + self, refresh_token: models.RefreshToken + ) -> None: """Delete a refresh token.""" await self._store.async_remove_refresh_token(refresh_token) @callback - def async_create_access_token(self, - refresh_token: models.RefreshToken, - remote_ip: Optional[str] = None) -> str: + def async_create_access_token( + self, 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': now, - 'exp': now + refresh_token.access_token_expiration, - }, refresh_token.jwt_key, algorithm='HS256').decode() + return jwt.encode( + { + "iss": refresh_token.id, + "iat": now, + "exp": now + refresh_token.access_token_expiration, + }, + refresh_token.jwt_key, + algorithm="HS256", + ).decode() async def async_validate_access_token( - self, token: str) -> Optional[models.RefreshToken]: + self, token: str + ) -> Optional[models.RefreshToken]: """Return refresh token if an access token is valid.""" try: unverif_claims = jwt.decode(token, verify=False) @@ -331,23 +354,18 @@ class AuthManager: return None refresh_token = await self.async_get_refresh_token( - cast(str, unverif_claims.get('iss'))) + cast(str, unverif_claims.get("iss")) + ) if refresh_token is None: - jwt_key = '' - issuer = '' + jwt_key = "" + issuer = "" else: jwt_key = refresh_token.jwt_key issuer = refresh_token.id try: - jwt.decode( - token, - jwt_key, - leeway=10, - issuer=issuer, - algorithms=['HS256'] - ) + jwt.decode(token, jwt_key, leeway=10, issuer=issuer, algorithms=["HS256"]) except jwt.InvalidTokenError: return None @@ -357,31 +375,32 @@ class AuthManager: return refresh_token async def _async_create_login_flow( - self, handler: _ProviderKey, *, context: Optional[Dict], - data: Optional[Any]) -> data_entry_flow.FlowHandler: + self, handler: _ProviderKey, *, context: Optional[Dict], data: Optional[Any] + ) -> data_entry_flow.FlowHandler: """Create a login flow.""" auth_provider = self._providers[handler] return await auth_provider.async_login_flow(context) async def _async_finish_login_flow( - self, flow: LoginFlow, result: Dict[str, Any]) \ - -> Dict[str, Any]: + self, flow: LoginFlow, result: Dict[str, Any] + ) -> Dict[str, Any]: """Return a user as result of login flow.""" - if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: return result # we got final result - if isinstance(result['data'], models.User): - result['result'] = result['data'] + if isinstance(result["data"], models.User): + result["result"] = result["data"] return result - auth_provider = self._providers[result['handler']] + auth_provider = self._providers[result["handler"]] credentials = await auth_provider.async_get_or_create_credentials( - result['data']) + result["data"] + ) - if flow.context is not None and flow.context.get('credential_only'): - result['result'] = credentials + if flow.context is not None and flow.context.get("credential_only"): + result["result"] = credentials return result # multi-factor module cannot enabled for new credential @@ -396,15 +415,18 @@ class AuthManager: flow.available_mfa_modules = modules return await flow.async_step_select_mfa_module() - result['result'] = await self.async_get_or_create_user(credentials) + result["result"] = await self.async_get_or_create_user(credentials) return result @callback def _async_get_auth_provider( - self, credentials: models.Credentials) -> Optional[AuthProvider]: + self, credentials: models.Credentials + ) -> Optional[AuthProvider]: """Get auth provider from a set of credentials.""" - auth_provider_key = (credentials.auth_provider_type, - credentials.auth_provider_id) + auth_provider_key = ( + credentials.auth_provider_type, + credentials.auth_provider_id, + ) return self._providers.get(auth_provider_key) async def _user_should_be_owner(self) -> bool: diff --git a/homeassistant/auth/auth_store.py b/homeassistant/auth/auth_store.py index fb4700c80..bd773702f 100644 --- a/homeassistant/auth/auth_store.py +++ b/homeassistant/auth/auth_store.py @@ -12,7 +12,7 @@ from homeassistant.util import dt as dt_util from . import models STORAGE_VERSION = 1 -STORAGE_KEY = 'auth' +STORAGE_KEY = "auth" class AuthStore: @@ -47,27 +47,28 @@ class AuthStore: return self._users.get(user_id) async def async_create_user( - self, name: Optional[str], is_owner: Optional[bool] = None, - is_active: Optional[bool] = None, - system_generated: Optional[bool] = None, - credentials: Optional[models.Credentials] = None) -> models.User: + self, + name: Optional[str], + is_owner: Optional[bool] = None, + is_active: Optional[bool] = None, + system_generated: Optional[bool] = None, + credentials: Optional[models.Credentials] = None, + ) -> models.User: """Create a new user.""" if self._users is None: await self._async_load() assert self._users is not None - kwargs = { - 'name': name - } # type: Dict[str, Any] + kwargs = {"name": name} # type: Dict[str, Any] if is_owner is not None: - kwargs['is_owner'] = is_owner + kwargs["is_owner"] = is_owner if is_active is not None: - kwargs['is_active'] = is_active + kwargs["is_active"] = is_active if system_generated is not None: - kwargs['system_generated'] = system_generated + kwargs["system_generated"] = system_generated new_user = models.User(**kwargs) @@ -81,8 +82,9 @@ class AuthStore: await self.async_link_user(new_user, credentials) return new_user - async def async_link_user(self, user: models.User, - credentials: models.Credentials) -> None: + async def async_link_user( + self, user: models.User, credentials: models.Credentials + ) -> None: """Add credentials to an existing user.""" user.credentials.append(credentials) self._async_schedule_save() @@ -107,8 +109,7 @@ class AuthStore: user.is_active = False self._async_schedule_save() - async def async_remove_credentials( - self, credentials: models.Credentials) -> None: + async def async_remove_credentials(self, credentials: models.Credentials) -> None: """Remove credentials.""" if self._users is None: await self._async_load() @@ -129,23 +130,25 @@ class AuthStore: self._async_schedule_save() 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: str = models.TOKEN_TYPE_NORMAL, - access_token_expiration: timedelta = ACCESS_TOKEN_EXPIRATION) \ - -> models.RefreshToken: + 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.""" kwargs = { - 'user': user, - 'client_id': client_id, - 'token_type': token_type, - 'access_token_expiration': access_token_expiration + "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 + kwargs["client_name"] = client_name if client_icon: - kwargs['client_icon'] = client_icon + kwargs["client_icon"] = client_icon refresh_token = models.RefreshToken(**kwargs) user.refresh_tokens[refresh_token.id] = refresh_token @@ -154,7 +157,8 @@ class AuthStore: return refresh_token async def async_remove_refresh_token( - self, refresh_token: models.RefreshToken) -> None: + self, refresh_token: models.RefreshToken + ) -> None: """Remove a refresh token.""" if self._users is None: await self._async_load() @@ -166,7 +170,8 @@ class AuthStore: break async def async_get_refresh_token( - self, token_id: str) -> Optional[models.RefreshToken]: + self, token_id: str + ) -> Optional[models.RefreshToken]: """Get refresh token by id.""" if self._users is None: await self._async_load() @@ -180,7 +185,8 @@ class AuthStore: return None async def async_get_refresh_token_by_token( - self, token: str) -> Optional[models.RefreshToken]: + self, token: str + ) -> Optional[models.RefreshToken]: """Get refresh token by token.""" if self._users is None: await self._async_load() @@ -197,8 +203,8 @@ class AuthStore: @callback def async_log_refresh_token_usage( - self, refresh_token: models.RefreshToken, - remote_ip: Optional[str] = None) -> None: + 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 @@ -219,61 +225,66 @@ class AuthStore: self._users = users return - for user_dict in data['users']: - users[user_dict['id']] = models.User(**user_dict) + for user_dict in data["users"]: + users[user_dict["id"]] = models.User(**user_dict) - for cred_dict in data['credentials']: - users[cred_dict['user_id']].credentials.append(models.Credentials( - id=cred_dict['id'], - is_new=False, - auth_provider_type=cred_dict['auth_provider_type'], - auth_provider_id=cred_dict['auth_provider_id'], - data=cred_dict['data'], - )) + for cred_dict in data["credentials"]: + users[cred_dict["user_id"]].credentials.append( + models.Credentials( + id=cred_dict["id"], + is_new=False, + auth_provider_type=cred_dict["auth_provider_type"], + auth_provider_id=cred_dict["auth_provider_id"], + data=cred_dict["data"], + ) + ) - for rt_dict in data['refresh_tokens']: + for rt_dict in data["refresh_tokens"]: # Filter out the old keys that don't have jwt_key (pre-0.76) - if 'jwt_key' not in rt_dict: + if "jwt_key" not in rt_dict: continue - created_at = dt_util.parse_datetime(rt_dict['created_at']) + created_at = dt_util.parse_datetime(rt_dict["created_at"]) if created_at is None: getLogger(__name__).error( - 'Ignoring refresh token %(id)s with invalid created_at ' - '%(created_at)s for user_id %(user_id)s', rt_dict) + "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') + token_type = rt_dict.get("token_type") if token_type is None: - if rt_dict['client_id'] 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') + 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'], + 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'), + 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'], + seconds=rt_dict["access_token_expiration"] + ), + token=rt_dict["token"], + jwt_key=rt_dict["jwt_key"], last_used_at=last_used_at, - last_used_ip=rt_dict.get('last_used_ip'), + last_used_ip=rt_dict.get("last_used_ip"), ) - users[rt_dict['user_id']].refresh_tokens[token.id] = token + users[rt_dict["user_id"]].refresh_tokens[token.id] = token self._users = users @@ -292,22 +303,22 @@ class AuthStore: users = [ { - 'id': user.id, - 'is_owner': user.is_owner, - 'is_active': user.is_active, - 'name': user.name, - 'system_generated': user.system_generated, + "id": user.id, + "is_owner": user.is_owner, + "is_active": user.is_active, + "name": user.name, + "system_generated": user.system_generated, } for user in self._users.values() ] credentials = [ { - 'id': credential.id, - 'user_id': user.id, - 'auth_provider_type': credential.auth_provider_type, - 'auth_provider_id': credential.auth_provider_id, - 'data': credential.data, + "id": credential.id, + "user_id": user.id, + "auth_provider_type": credential.auth_provider_type, + "auth_provider_id": credential.auth_provider_id, + "data": credential.data, } for user in self._users.values() for credential in user.credentials @@ -315,28 +326,27 @@ class AuthStore: refresh_tokens = [ { - '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, + "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() ] return { - 'users': users, - 'credentials': credentials, - 'refresh_tokens': refresh_tokens, + "users": users, + "credentials": credentials, + "refresh_tokens": refresh_tokens, } diff --git a/homeassistant/auth/mfa_modules/__init__.py b/homeassistant/auth/mfa_modules/__init__.py index 603ca6ff3..867089b63 100644 --- a/homeassistant/auth/mfa_modules/__init__.py +++ b/homeassistant/auth/mfa_modules/__init__.py @@ -16,16 +16,19 @@ from homeassistant.util.decorator import Registry MULTI_FACTOR_AUTH_MODULES = Registry() -MULTI_FACTOR_AUTH_MODULE_SCHEMA = vol.Schema({ - vol.Required(CONF_TYPE): str, - vol.Optional(CONF_NAME): str, - # Specify ID if you have two mfa auth module for same type. - vol.Optional(CONF_ID): str, -}, extra=vol.ALLOW_EXTRA) +MULTI_FACTOR_AUTH_MODULE_SCHEMA = vol.Schema( + { + vol.Required(CONF_TYPE): str, + vol.Optional(CONF_NAME): str, + # Specify ID if you have two mfa auth module for same type. + vol.Optional(CONF_ID): str, + }, + extra=vol.ALLOW_EXTRA, +) SESSION_EXPIRATION = timedelta(minutes=5) -DATA_REQS = 'mfa_auth_module_reqs_processed' +DATA_REQS = "mfa_auth_module_reqs_processed" _LOGGER = logging.getLogger(__name__) @@ -33,7 +36,7 @@ _LOGGER = logging.getLogger(__name__) class MultiFactorAuthModule: """Multi-factor Auth Module of validation function.""" - DEFAULT_TITLE = 'Unnamed auth module' + DEFAULT_TITLE = "Unnamed auth module" def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: """Initialize an auth module.""" @@ -65,7 +68,7 @@ class MultiFactorAuthModule: """Return a voluptuous schema to define mfa auth module's input.""" raise NotImplementedError - async def async_setup_flow(self, user_id: str) -> 'SetupFlow': + async def async_setup_flow(self, user_id: str) -> "SetupFlow": """Return a data entry flow handler for setup module. Mfa module should extend SetupFlow @@ -84,8 +87,7 @@ class MultiFactorAuthModule: """Return whether user is setup.""" raise NotImplementedError - async def async_validation( - self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validation(self, user_id: str, user_input: Dict[str, Any]) -> bool: """Return True if validation passed.""" raise NotImplementedError @@ -93,17 +95,17 @@ class MultiFactorAuthModule: class SetupFlow(data_entry_flow.FlowHandler): """Handler for the setup flow.""" - def __init__(self, auth_module: MultiFactorAuthModule, - setup_schema: vol.Schema, - user_id: str) -> None: + def __init__( + self, auth_module: MultiFactorAuthModule, setup_schema: vol.Schema, user_id: str + ) -> None: """Initialize the setup flow.""" self._auth_module = auth_module self._setup_schema = setup_schema self._user_id = user_id async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the first step of setup flow. Return self.async_show_form(step_id='init') if user_input == None. @@ -112,23 +114,19 @@ class SetupFlow(data_entry_flow.FlowHandler): errors = {} # type: Dict[str, str] if user_input: - result = await self._auth_module.async_setup_user( - self._user_id, user_input) + result = await self._auth_module.async_setup_user(self._user_id, user_input) return self.async_create_entry( - title=self._auth_module.name, - data={'result': result} + title=self._auth_module.name, data={"result": result} ) return self.async_show_form( - step_id='init', - data_schema=self._setup_schema, - errors=errors + step_id="init", data_schema=self._setup_schema, errors=errors ) async def auth_mfa_module_from_config( - hass: HomeAssistant, config: Dict[str, Any]) \ - -> MultiFactorAuthModule: + hass: HomeAssistant, config: Dict[str, Any] +) -> MultiFactorAuthModule: """Initialize an auth module from a config.""" module_name = config[CONF_TYPE] module = await _load_mfa_module(hass, module_name) @@ -136,26 +134,29 @@ async def auth_mfa_module_from_config( try: config = module.CONFIG_SCHEMA(config) # type: ignore except vol.Invalid as err: - _LOGGER.error('Invalid configuration for multi-factor module %s: %s', - module_name, humanize_error(config, err)) + _LOGGER.error( + "Invalid configuration for multi-factor module %s: %s", + module_name, + humanize_error(config, err), + ) raise return MULTI_FACTOR_AUTH_MODULES[module_name](hass, config) # type: ignore -async def _load_mfa_module(hass: HomeAssistant, module_name: str) \ - -> types.ModuleType: +async def _load_mfa_module(hass: HomeAssistant, module_name: str) -> types.ModuleType: """Load an mfa auth module.""" - module_path = 'homeassistant.auth.mfa_modules.{}'.format(module_name) + module_path = "homeassistant.auth.mfa_modules.{}".format(module_name) try: module = importlib.import_module(module_path) except ImportError as err: - _LOGGER.error('Unable to load mfa module %s: %s', module_name, err) - raise HomeAssistantError('Unable to load mfa module {}: {}'.format( - module_name, err)) + _LOGGER.error("Unable to load mfa module %s: %s", module_name, err) + raise HomeAssistantError( + "Unable to load mfa module {}: {}".format(module_name, err) + ) - if hass.config.skip_pip or not hasattr(module, 'REQUIREMENTS'): + if hass.config.skip_pip or not hasattr(module, "REQUIREMENTS"): return module processed = hass.data.get(DATA_REQS) @@ -166,12 +167,13 @@ async def _load_mfa_module(hass: HomeAssistant, module_name: str) \ # https://github.com/python/mypy/issues/1424 req_success = await requirements.async_process_requirements( - hass, module_path, module.REQUIREMENTS) # type: ignore + hass, module_path, module.REQUIREMENTS + ) # type: ignore if not req_success: raise HomeAssistantError( - 'Unable to process requirements of mfa module {}'.format( - module_name)) + "Unable to process requirements of mfa module {}".format(module_name) + ) processed.add(module_name) return module diff --git a/homeassistant/auth/mfa_modules/insecure_example.py b/homeassistant/auth/mfa_modules/insecure_example.py index 9c72111ef..ba131ee0a 100644 --- a/homeassistant/auth/mfa_modules/insecure_example.py +++ b/homeassistant/auth/mfa_modules/insecure_example.py @@ -6,39 +6,45 @@ import voluptuous as vol from homeassistant.core import HomeAssistant -from . import MultiFactorAuthModule, MULTI_FACTOR_AUTH_MODULES, \ - MULTI_FACTOR_AUTH_MODULE_SCHEMA, SetupFlow +from . import ( + MultiFactorAuthModule, + MULTI_FACTOR_AUTH_MODULES, + MULTI_FACTOR_AUTH_MODULE_SCHEMA, + SetupFlow, +) -CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({ - vol.Required('data'): [vol.Schema({ - vol.Required('user_id'): str, - vol.Required('pin'): str, - })] -}, extra=vol.PREVENT_EXTRA) +CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend( + { + vol.Required("data"): [ + vol.Schema({vol.Required("user_id"): str, vol.Required("pin"): str}) + ] + }, + extra=vol.PREVENT_EXTRA, +) _LOGGER = logging.getLogger(__name__) -@MULTI_FACTOR_AUTH_MODULES.register('insecure_example') +@MULTI_FACTOR_AUTH_MODULES.register("insecure_example") class InsecureExampleModule(MultiFactorAuthModule): """Example auth module validate pin.""" - DEFAULT_TITLE = 'Insecure Personal Identify Number' + DEFAULT_TITLE = "Insecure Personal Identify Number" def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: """Initialize the user data store.""" super().__init__(hass, config) - self._data = config['data'] + self._data = config["data"] @property def input_schema(self) -> vol.Schema: """Validate login flow input data.""" - return vol.Schema({'pin': str}) + return vol.Schema({"pin": str}) @property def setup_schema(self) -> vol.Schema: """Validate async_setup_user input data.""" - return vol.Schema({'pin': str}) + return vol.Schema({"pin": str}) async def async_setup_flow(self, user_id: str) -> SetupFlow: """Return a data entry flow handler for setup module. @@ -50,21 +56,21 @@ class InsecureExampleModule(MultiFactorAuthModule): async def async_setup_user(self, user_id: str, setup_data: Any) -> Any: """Set up user to use mfa module.""" # data shall has been validate in caller - pin = setup_data['pin'] + pin = setup_data["pin"] for data in self._data: - if data['user_id'] == user_id: + if data["user_id"] == user_id: # already setup, override - data['pin'] = pin + data["pin"] = pin return - self._data.append({'user_id': user_id, 'pin': pin}) + self._data.append({"user_id": user_id, "pin": pin}) async def async_depose_user(self, user_id: str) -> None: """Remove user from mfa module.""" found = None for data in self._data: - if data['user_id'] == user_id: + if data["user_id"] == user_id: found = data break if found: @@ -73,17 +79,16 @@ class InsecureExampleModule(MultiFactorAuthModule): async def async_is_user_setup(self, user_id: str) -> bool: """Return whether user is setup.""" for data in self._data: - if data['user_id'] == user_id: + if data["user_id"] == user_id: return True return False - async def async_validation( - self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validation(self, user_id: str, user_input: Dict[str, Any]) -> bool: """Return True if validation passed.""" for data in self._data: - if data['user_id'] == user_id: + if data["user_id"] == user_id: # user_input has been validate in caller - if data['pin'] == user_input['pin']: + if data["pin"] == user_input["pin"]: return True return False diff --git a/homeassistant/auth/mfa_modules/totp.py b/homeassistant/auth/mfa_modules/totp.py index 50cd9d334..1500c7c4d 100644 --- a/homeassistant/auth/mfa_modules/totp.py +++ b/homeassistant/auth/mfa_modules/totp.py @@ -8,23 +8,26 @@ import voluptuous as vol from homeassistant.auth.models import User from homeassistant.core import HomeAssistant -from . import MultiFactorAuthModule, MULTI_FACTOR_AUTH_MODULES, \ - MULTI_FACTOR_AUTH_MODULE_SCHEMA, SetupFlow +from . import ( + MultiFactorAuthModule, + MULTI_FACTOR_AUTH_MODULES, + MULTI_FACTOR_AUTH_MODULE_SCHEMA, + SetupFlow, +) -REQUIREMENTS = ['pyotp==2.2.6', 'PyQRCode==1.2.1'] +REQUIREMENTS = ["pyotp==2.2.6", "PyQRCode==1.2.1"] -CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({ -}, extra=vol.PREVENT_EXTRA) +CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({}, extra=vol.PREVENT_EXTRA) STORAGE_VERSION = 1 -STORAGE_KEY = 'auth_module.totp' -STORAGE_USERS = 'users' -STORAGE_USER_ID = 'user_id' -STORAGE_OTA_SECRET = 'ota_secret' +STORAGE_KEY = "auth_module.totp" +STORAGE_USERS = "users" +STORAGE_USER_ID = "user_id" +STORAGE_OTA_SECRET = "ota_secret" -INPUT_FIELD_CODE = 'code' +INPUT_FIELD_CODE = "code" -DUMMY_SECRET = 'FPPTH34D4E3MI2HG' +DUMMY_SECRET = "FPPTH34D4E3MI2HG" _LOGGER = logging.getLogger(__name__) @@ -37,10 +40,15 @@ def _generate_qr_code(data: str) -> str: with BytesIO() as buffer: qr_code.svg(file=buffer, scale=4) - return '{}'.format( - buffer.getvalue().decode("ascii").replace('\n', '') - .replace('' - '' + ' Tuple[str, str, str]: ota_secret = pyotp.random_base32() url = pyotp.totp.TOTP(ota_secret).provisioning_uri( - username, issuer_name="Home Assistant") + username, issuer_name="Home Assistant" + ) image = _generate_qr_code(url) return ota_secret, url, image -@MULTI_FACTOR_AUTH_MODULES.register('totp') +@MULTI_FACTOR_AUTH_MODULES.register("totp") class TotpAuthModule(MultiFactorAuthModule): """Auth module validate time-based one time password.""" - DEFAULT_TITLE = 'Time-based One Time Password' + DEFAULT_TITLE = "Time-based One Time Password" def __init__(self, hass: HomeAssistant, config: Dict[str, Any]) -> None: """Initialize the user data store.""" super().__init__(hass, config) self._users = None # type: Optional[Dict[str, str]] - self._user_store = hass.helpers.storage.Store( - STORAGE_VERSION, STORAGE_KEY) + self._user_store = hass.helpers.storage.Store(STORAGE_VERSION, STORAGE_KEY) @property def input_schema(self) -> vol.Schema: @@ -86,14 +94,13 @@ class TotpAuthModule(MultiFactorAuthModule): """Save data.""" await self._user_store.async_save({STORAGE_USERS: self._users}) - def _add_ota_secret(self, user_id: str, - secret: Optional[str] = None) -> str: + def _add_ota_secret(self, user_id: str, secret: Optional[str] = None) -> str: """Create a ota_secret for user.""" import pyotp ota_secret = secret or pyotp.random_base32() # type: str - self._users[user_id] = ota_secret # type: ignore + self._users[user_id] = ota_secret # type: ignore return ota_secret async def async_setup_flow(self, user_id: str) -> SetupFlow: @@ -101,7 +108,7 @@ class TotpAuthModule(MultiFactorAuthModule): Mfa module should extend SetupFlow """ - user = await self.hass.auth.async_get_user(user_id) # type: ignore + user = await self.hass.auth.async_get_user(user_id) # type: ignore return TotpSetupFlow(self, self.input_schema, user) async def async_setup_user(self, user_id: str, setup_data: Any) -> str: @@ -110,7 +117,8 @@ class TotpAuthModule(MultiFactorAuthModule): await self._async_load() result = await self.hass.async_add_executor_job( - self._add_ota_secret, user_id, setup_data.get('secret')) + self._add_ota_secret, user_id, setup_data.get("secret") + ) await self._async_save() return result @@ -120,7 +128,7 @@ class TotpAuthModule(MultiFactorAuthModule): if self._users is None: await self._async_load() - if self._users.pop(user_id, None): # type: ignore + if self._users.pop(user_id, None): # type: ignore await self._async_save() async def async_is_user_setup(self, user_id: str) -> bool: @@ -128,10 +136,9 @@ class TotpAuthModule(MultiFactorAuthModule): if self._users is None: await self._async_load() - return user_id in self._users # type: ignore + return user_id in self._users # type: ignore - async def async_validation( - self, user_id: str, user_input: Dict[str, Any]) -> bool: + async def async_validation(self, user_id: str, user_input: Dict[str, Any]) -> bool: """Return True if validation passed.""" if self._users is None: await self._async_load() @@ -139,7 +146,8 @@ class TotpAuthModule(MultiFactorAuthModule): # user_input has been validate in caller # set INPUT_FIELD_CODE as vol.Required is not user friendly return await self.hass.async_add_executor_job( - self._validate_2fa, user_id, user_input.get(INPUT_FIELD_CODE, '')) + self._validate_2fa, user_id, user_input.get(INPUT_FIELD_CODE, "") + ) def _validate_2fa(self, user_id: str, code: str) -> bool: """Validate two factor authentication code.""" @@ -158,9 +166,9 @@ class TotpAuthModule(MultiFactorAuthModule): class TotpSetupFlow(SetupFlow): """Handler for the setup flow.""" - def __init__(self, auth_module: TotpAuthModule, - setup_schema: vol.Schema, - user: User) -> None: + def __init__( + self, auth_module: TotpAuthModule, setup_schema: vol.Schema, user: User + ) -> None: """Initialize the setup flow.""" super().__init__(auth_module, setup_schema, user.id) # to fix typing complaint @@ -171,8 +179,8 @@ class TotpSetupFlow(SetupFlow): self._image = None # type Optional[str] async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the first step of setup flow. Return self.async_show_form(step_id='init') if user_input == None. @@ -184,30 +192,31 @@ class TotpSetupFlow(SetupFlow): if user_input: verified = await self.hass.async_add_executor_job( # type: ignore - pyotp.TOTP(self._ota_secret).verify, user_input['code']) + pyotp.TOTP(self._ota_secret).verify, user_input["code"] + ) if verified: result = await self._auth_module.async_setup_user( - self._user_id, {'secret': self._ota_secret}) + self._user_id, {"secret": self._ota_secret} + ) return self.async_create_entry( - title=self._auth_module.name, - data={'result': result} + title=self._auth_module.name, data={"result": result} ) - errors['base'] = 'invalid_code' + errors["base"] = "invalid_code" else: hass = self._auth_module.hass - self._ota_secret, self._url, self._image = \ - await hass.async_add_executor_job( # type: ignore - _generate_secret_and_qr_code, str(self._user.name)) + self._ota_secret, self._url, self._image = await hass.async_add_executor_job( # type: ignore + _generate_secret_and_qr_code, str(self._user.name) + ) return self.async_show_form( - step_id='init', + step_id="init", data_schema=self._setup_schema, description_placeholders={ - 'code': self._ota_secret, - 'url': self._url, - 'qr_code': self._image + "code": self._ota_secret, + "url": self._url, + "qr_code": self._image, }, - errors=errors + errors=errors, ) diff --git a/homeassistant/auth/models.py b/homeassistant/auth/models.py index b0f4024c3..b4feb118d 100644 --- a/homeassistant/auth/models.py +++ b/homeassistant/auth/models.py @@ -9,9 +9,9 @@ from homeassistant.util import dt as dt_util from .util import generate_secret -TOKEN_TYPE_NORMAL = 'normal' -TOKEN_TYPE_SYSTEM = 'system' -TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = 'long_lived_access_token' +TOKEN_TYPE_NORMAL = "normal" +TOKEN_TYPE_SYSTEM = "system" +TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN = "long_lived_access_token" @attr.s(slots=True) @@ -44,16 +44,17 @@ class RefreshToken: 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))) + 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)) - 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))) + 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) @@ -73,5 +74,4 @@ class Credentials: is_new = attr.ib(type=bool, default=True) -UserMeta = NamedTuple("UserMeta", - [('name', Optional[str]), ('is_active', bool)]) +UserMeta = NamedTuple("UserMeta", [("name", Optional[str]), ("is_active", bool)]) diff --git a/homeassistant/auth/providers/__init__.py b/homeassistant/auth/providers/__init__.py index 3cb1c6b12..360aa0b68 100644 --- a/homeassistant/auth/providers/__init__.py +++ b/homeassistant/auth/providers/__init__.py @@ -19,25 +19,29 @@ from ..models import Credentials, User, UserMeta # noqa: F401 from ..mfa_modules import SESSION_EXPIRATION _LOGGER = logging.getLogger(__name__) -DATA_REQS = 'auth_prov_reqs_processed' +DATA_REQS = "auth_prov_reqs_processed" AUTH_PROVIDERS = Registry() -AUTH_PROVIDER_SCHEMA = vol.Schema({ - vol.Required(CONF_TYPE): str, - vol.Optional(CONF_NAME): str, - # Specify ID if you have two auth providers for same type. - vol.Optional(CONF_ID): str, -}, extra=vol.ALLOW_EXTRA) +AUTH_PROVIDER_SCHEMA = vol.Schema( + { + vol.Required(CONF_TYPE): str, + vol.Optional(CONF_NAME): str, + # Specify ID if you have two auth providers for same type. + vol.Optional(CONF_ID): str, + }, + extra=vol.ALLOW_EXTRA, +) class AuthProvider: """Provider of user authentication.""" - DEFAULT_TITLE = 'Unnamed auth provider' + DEFAULT_TITLE = "Unnamed auth provider" - def __init__(self, hass: HomeAssistant, store: AuthStore, - config: Dict[str, Any]) -> None: + def __init__( + self, hass: HomeAssistant, store: AuthStore, config: Dict[str, Any] + ) -> None: """Initialize an auth provider.""" self.hass = hass self.store = store @@ -73,22 +77,22 @@ class AuthProvider: credentials for user in users for credentials in user.credentials - if (credentials.auth_provider_type == self.type and - credentials.auth_provider_id == self.id) + if ( + credentials.auth_provider_type == self.type + and credentials.auth_provider_id == self.id + ) ] @callback def async_create_credentials(self, data: Dict[str, str]) -> Credentials: """Create credentials.""" return Credentials( - auth_provider_type=self.type, - auth_provider_id=self.id, - data=data, + auth_provider_type=self.type, auth_provider_id=self.id, data=data ) # Implement by extending class - async def async_login_flow(self, context: Optional[Dict]) -> 'LoginFlow': + async def async_login_flow(self, context: Optional[Dict]) -> "LoginFlow": """Return the data flow for logging in with auth provider. Auth provider should extend LoginFlow and return an instance. @@ -96,12 +100,14 @@ class AuthProvider: raise NotImplementedError async def async_get_or_create_credentials( - self, flow_result: Dict[str, str]) -> Credentials: + self, flow_result: Dict[str, str] + ) -> Credentials: """Get credentials based on the flow result.""" raise NotImplementedError async def async_user_meta_for_credentials( - self, credentials: Credentials) -> UserMeta: + self, credentials: Credentials + ) -> UserMeta: """Return extra user metadata for credentials. Will be used to populate info when creating a new user. @@ -110,8 +116,8 @@ class AuthProvider: async def auth_provider_from_config( - hass: HomeAssistant, store: AuthStore, - config: Dict[str, Any]) -> AuthProvider: + hass: HomeAssistant, store: AuthStore, config: Dict[str, Any] +) -> AuthProvider: """Initialize an auth provider from a config.""" provider_name = config[CONF_TYPE] module = await load_auth_provider_module(hass, provider_name) @@ -119,25 +125,31 @@ async def auth_provider_from_config( try: config = module.CONFIG_SCHEMA(config) # type: ignore except vol.Invalid as err: - _LOGGER.error('Invalid configuration for auth provider %s: %s', - provider_name, humanize_error(config, err)) + _LOGGER.error( + "Invalid configuration for auth provider %s: %s", + provider_name, + humanize_error(config, err), + ) raise return AUTH_PROVIDERS[provider_name](hass, store, config) # type: ignore async def load_auth_provider_module( - hass: HomeAssistant, provider: str) -> types.ModuleType: + hass: HomeAssistant, provider: str +) -> types.ModuleType: """Load an auth provider.""" try: module = importlib.import_module( - 'homeassistant.auth.providers.{}'.format(provider)) + "homeassistant.auth.providers.{}".format(provider) + ) except ImportError as err: - _LOGGER.error('Unable to load auth provider %s: %s', provider, err) - raise HomeAssistantError('Unable to load auth provider {}: {}'.format( - provider, err)) + _LOGGER.error("Unable to load auth provider %s: %s", provider, err) + raise HomeAssistantError( + "Unable to load auth provider {}: {}".format(provider, err) + ) - if hass.config.skip_pip or not hasattr(module, 'REQUIREMENTS'): + if hass.config.skip_pip or not hasattr(module, "REQUIREMENTS"): return module processed = hass.data.get(DATA_REQS) @@ -150,12 +162,13 @@ async def load_auth_provider_module( # https://github.com/python/mypy/issues/1424 reqs = module.REQUIREMENTS # type: ignore req_success = await requirements.async_process_requirements( - hass, 'auth provider {}'.format(provider), reqs) + hass, "auth provider {}".format(provider), reqs + ) if not req_success: raise HomeAssistantError( - 'Unable to process requirements of auth provider {}'.format( - provider)) + "Unable to process requirements of auth provider {}".format(provider) + ) processed.add(provider) return module @@ -174,8 +187,8 @@ class LoginFlow(data_entry_flow.FlowHandler): self.user = None # type: Optional[User] async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the first step of login flow. Return self.async_show_form(step_id='init') if user_input == None. @@ -184,38 +197,37 @@ class LoginFlow(data_entry_flow.FlowHandler): raise NotImplementedError async def async_step_select_mfa_module( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the step of select mfa module.""" errors = {} if user_input is not None: - auth_module = user_input.get('multi_factor_auth_module') + auth_module = user_input.get("multi_factor_auth_module") if auth_module in self.available_mfa_modules: self._auth_module_id = auth_module return await self.async_step_mfa() - errors['base'] = 'invalid_auth_module' + errors["base"] = "invalid_auth_module" if len(self.available_mfa_modules) == 1: self._auth_module_id = list(self.available_mfa_modules.keys())[0] return await self.async_step_mfa() return self.async_show_form( - step_id='select_mfa_module', - data_schema=vol.Schema({ - 'multi_factor_auth_module': vol.In(self.available_mfa_modules) - }), + step_id="select_mfa_module", + data_schema=vol.Schema( + {"multi_factor_auth_module": vol.In(self.available_mfa_modules)} + ), errors=errors, ) async def async_step_mfa( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the step of mfa validation.""" errors = {} - auth_module = self._auth_manager.get_auth_mfa_module( - self._auth_module_id) + auth_module = self._auth_manager.get_auth_mfa_module(self._auth_module_id) if auth_module is None: # Given an invalid input to async_step_select_mfa_module # will show invalid_auth_module error @@ -224,25 +236,24 @@ class LoginFlow(data_entry_flow.FlowHandler): if user_input is not None: expires = self.created_at + SESSION_EXPIRATION if dt_util.utcnow() > expires: - return self.async_abort( - reason='login_expired' - ) + return self.async_abort(reason="login_expired") result = await auth_module.async_validation( - self.user.id, user_input) # type: ignore + self.user.id, user_input + ) # type: ignore if not result: - errors['base'] = 'invalid_code' + errors["base"] = "invalid_code" if not errors: return await self.async_finish(self.user) description_placeholders = { - 'mfa_module_name': auth_module.name, - 'mfa_module_id': auth_module.id + "mfa_module_name": auth_module.name, + "mfa_module_id": auth_module.id, } # type: Dict[str, str] return self.async_show_form( - step_id='mfa', + step_id="mfa", data_schema=auth_module.input_schema, description_placeholders=description_placeholders, errors=errors, @@ -250,7 +261,4 @@ class LoginFlow(data_entry_flow.FlowHandler): async def async_finish(self, flow_result: Any) -> Dict: """Handle the pass of login flow.""" - return self.async_create_entry( - title=self._auth_provider.name, - data=flow_result - ) + return self.async_create_entry(title=self._auth_provider.name, data=flow_result) diff --git a/homeassistant/auth/providers/homeassistant.py b/homeassistant/auth/providers/homeassistant.py index c743a5b7f..149415e6c 100644 --- a/homeassistant/auth/providers/homeassistant.py +++ b/homeassistant/auth/providers/homeassistant.py @@ -20,14 +20,13 @@ from ..util import generate_secret STORAGE_VERSION = 1 -STORAGE_KEY = 'auth_provider.homeassistant' +STORAGE_KEY = "auth_provider.homeassistant" def _disallow_id(conf: Dict[str, Any]) -> Dict[str, Any]: """Disallow ID in config.""" if CONF_ID in conf: - raise vol.Invalid( - 'ID is not allowed for the homeassistant auth provider.') + raise vol.Invalid("ID is not allowed for the homeassistant auth provider.") return conf @@ -60,68 +59,62 @@ class Data: data = await self._store.async_load() if data is None: - data = { - 'salt': generate_secret(), - 'users': [] - } + data = {"salt": generate_secret(), "users": []} self._data = data @property def users(self) -> List[Dict[str, str]]: """Return users.""" - return self._data['users'] # type: ignore + return self._data["users"] # type: ignore def validate_login(self, username: str, password: str) -> None: """Validate a username and password. Raises InvalidAuth if auth invalid. """ - dummy = b'$2b$12$CiuFGszHx9eNHxPuQcwBWez4CwDTOcLTX5CbOpV6gef2nYuXkY7BO' + dummy = b"$2b$12$CiuFGszHx9eNHxPuQcwBWez4CwDTOcLTX5CbOpV6gef2nYuXkY7BO" found = None # Compare all users to avoid timing attacks. for user in self.users: - if username == user['username']: + if username == user["username"]: found = user if found is None: # check a hash to make timing the same as if user was found - bcrypt.checkpw(b'foo', - dummy) + bcrypt.checkpw(b"foo", dummy) raise InvalidAuth - user_hash = base64.b64decode(found['password']) + user_hash = base64.b64decode(found["password"]) # if the hash is not a bcrypt hash... # provide a transparant upgrade for old pbkdf2 hash format - if not (user_hash.startswith(b'$2a$') - or user_hash.startswith(b'$2b$') - or user_hash.startswith(b'$2x$') - or user_hash.startswith(b'$2y$')): + if not ( + user_hash.startswith(b"$2a$") + or user_hash.startswith(b"$2b$") + or user_hash.startswith(b"$2x$") + or user_hash.startswith(b"$2y$") + ): # IMPORTANT! validate the login, bail if invalid hashed = self.legacy_hash_password(password) if not hmac.compare_digest(hashed, user_hash): raise InvalidAuth # then re-hash the valid password with bcrypt - self.change_password(found['username'], password) - run_coroutine_threadsafe( - self.async_save(), self.hass.loop - ).result() - user_hash = base64.b64decode(found['password']) + self.change_password(found["username"], password) + run_coroutine_threadsafe(self.async_save(), self.hass.loop).result() + user_hash = base64.b64decode(found["password"]) # bcrypt.checkpw is timing-safe - if not bcrypt.checkpw(password.encode(), - user_hash): + if not bcrypt.checkpw(password.encode(), user_hash): raise InvalidAuth - def legacy_hash_password(self, password: str, - for_storage: bool = False) -> bytes: + def legacy_hash_password(self, password: str, for_storage: bool = False) -> bytes: """LEGACY password encoding.""" # We're no longer storing salts in data, but if one exists we # should be able to retrieve it. - salt = self._data['salt'].encode() # type: ignore - hashed = hashlib.pbkdf2_hmac('sha512', password.encode(), salt, 100000) + salt = self._data["salt"].encode() # type: ignore + hashed = hashlib.pbkdf2_hmac("sha512", password.encode(), salt, 100000) if for_storage: hashed = base64.b64encode(hashed) return hashed @@ -129,28 +122,30 @@ class Data: # pylint: disable=no-self-use def hash_password(self, password: str, for_storage: bool = False) -> bytes: """Encode a password.""" - hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12)) \ - # type: bytes + hashed = bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12)) + # type: bytes if for_storage: hashed = base64.b64encode(hashed) return hashed def add_auth(self, username: str, password: str) -> None: """Add a new authenticated user/pass.""" - if any(user['username'] == username for user in self.users): + if any(user["username"] == username for user in self.users): raise InvalidUser - self.users.append({ - 'username': username, - 'password': self.hash_password(password, True).decode(), - }) + self.users.append( + { + "username": username, + "password": self.hash_password(password, True).decode(), + } + ) @callback def async_remove_auth(self, username: str) -> None: """Remove authentication.""" index = None for i, user in enumerate(self.users): - if user['username'] == username: + if user["username"] == username: index = i break @@ -165,9 +160,8 @@ class Data: Raises InvalidUser if user cannot be found. """ for user in self.users: - if user['username'] == username: - user['password'] = self.hash_password( - new_password, True).decode() + if user["username"] == username: + user["password"] = self.hash_password(new_password, True).decode() break else: raise InvalidUser @@ -177,11 +171,11 @@ class Data: await self._store.async_save(self._data) -@AUTH_PROVIDERS.register('homeassistant') +@AUTH_PROVIDERS.register("homeassistant") class HassAuthProvider(AuthProvider): """Auth provider based on a local storage of users in HASS config dir.""" - DEFAULT_TITLE = 'Home Assistant Local' + DEFAULT_TITLE = "Home Assistant Local" data = None @@ -193,8 +187,7 @@ class HassAuthProvider(AuthProvider): self.data = Data(self.hass) await self.data.async_load() - async def async_login_flow( - self, context: Optional[Dict]) -> LoginFlow: + async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: """Return a flow to login.""" return HassLoginFlow(self) @@ -205,36 +198,36 @@ class HassAuthProvider(AuthProvider): assert self.data is not None await self.hass.async_add_executor_job( - self.data.validate_login, username, password) + self.data.validate_login, username, password + ) async def async_get_or_create_credentials( - self, flow_result: Dict[str, str]) -> Credentials: + self, flow_result: Dict[str, str] + ) -> Credentials: """Get credentials based on the flow result.""" - username = flow_result['username'] + username = flow_result["username"] for credential in await self.async_credentials(): - if credential.data['username'] == username: + if credential.data["username"] == username: return credential # Create new credentials. - return self.async_create_credentials({ - 'username': username - }) + return self.async_create_credentials({"username": username}) async def async_user_meta_for_credentials( - self, credentials: Credentials) -> UserMeta: + self, credentials: Credentials + ) -> UserMeta: """Get extra info for this credential.""" - return UserMeta(name=credentials.data['username'], is_active=True) + return UserMeta(name=credentials.data["username"], is_active=True) - async def async_will_remove_credentials( - self, credentials: Credentials) -> None: + async def async_will_remove_credentials(self, credentials: Credentials) -> None: """When credentials get removed, also remove the auth.""" if self.data is None: await self.async_initialize() assert self.data is not None try: - self.data.async_remove_auth(credentials.data['username']) + self.data.async_remove_auth(credentials.data["username"]) await self.data.async_save() except InvalidUser: # Can happen if somehow we didn't clean up a credential @@ -245,29 +238,27 @@ class HassLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the step of the form.""" errors = {} if user_input is not None: try: - await cast(HassAuthProvider, self._auth_provider)\ - .async_validate_login(user_input['username'], - user_input['password']) + await cast(HassAuthProvider, self._auth_provider).async_validate_login( + user_input["username"], user_input["password"] + ) except InvalidAuth: - errors['base'] = 'invalid_auth' + errors["base"] = "invalid_auth" if not errors: - user_input.pop('password') + user_input.pop("password") return await self.async_finish(user_input) schema = OrderedDict() # type: Dict[str, type] - schema['username'] = str - schema['password'] = str + schema["username"] = str + schema["password"] = str return self.async_show_form( - step_id='init', - data_schema=vol.Schema(schema), - errors=errors, + step_id="init", data_schema=vol.Schema(schema), errors=errors ) diff --git a/homeassistant/auth/providers/insecure_example.py b/homeassistant/auth/providers/insecure_example.py index 72e3dfe14..35524c3f5 100644 --- a/homeassistant/auth/providers/insecure_example.py +++ b/homeassistant/auth/providers/insecure_example.py @@ -12,23 +12,25 @@ from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow from ..models import Credentials, UserMeta -USER_SCHEMA = vol.Schema({ - vol.Required('username'): str, - vol.Required('password'): str, - vol.Optional('name'): str, -}) +USER_SCHEMA = vol.Schema( + { + vol.Required("username"): str, + vol.Required("password"): str, + vol.Optional("name"): str, + } +) -CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({ - vol.Required('users'): [USER_SCHEMA] -}, extra=vol.PREVENT_EXTRA) +CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend( + {vol.Required("users"): [USER_SCHEMA]}, extra=vol.PREVENT_EXTRA +) class InvalidAuthError(HomeAssistantError): """Raised when submitting invalid authentication.""" -@AUTH_PROVIDERS.register('insecure_example') +@AUTH_PROVIDERS.register("insecure_example") class ExampleAuthProvider(AuthProvider): """Example auth provider based on hardcoded usernames and passwords.""" @@ -42,47 +44,48 @@ class ExampleAuthProvider(AuthProvider): user = None # Compare all users to avoid timing attacks. - for usr in self.config['users']: - if hmac.compare_digest(username.encode('utf-8'), - usr['username'].encode('utf-8')): + for usr in self.config["users"]: + if hmac.compare_digest( + username.encode("utf-8"), usr["username"].encode("utf-8") + ): user = usr if user is None: # Do one more compare to make timing the same as if user was found. - hmac.compare_digest(password.encode('utf-8'), - password.encode('utf-8')) + hmac.compare_digest(password.encode("utf-8"), password.encode("utf-8")) raise InvalidAuthError - if not hmac.compare_digest(user['password'].encode('utf-8'), - password.encode('utf-8')): + if not hmac.compare_digest( + user["password"].encode("utf-8"), password.encode("utf-8") + ): raise InvalidAuthError async def async_get_or_create_credentials( - self, flow_result: Dict[str, str]) -> Credentials: + self, flow_result: Dict[str, str] + ) -> Credentials: """Get credentials based on the flow result.""" - username = flow_result['username'] + username = flow_result["username"] for credential in await self.async_credentials(): - if credential.data['username'] == username: + if credential.data["username"] == username: return credential # Create new credentials. - return self.async_create_credentials({ - 'username': username - }) + return self.async_create_credentials({"username": username}) async def async_user_meta_for_credentials( - self, credentials: Credentials) -> UserMeta: + self, credentials: Credentials + ) -> UserMeta: """Return extra user metadata for credentials. Will be used to populate info when creating a new user. """ - username = credentials.data['username'] + username = credentials.data["username"] name = None - for user in self.config['users']: - if user['username'] == username: - name = user.get('name') + for user in self.config["users"]: + if user["username"] == username: + name = user.get("name") break return UserMeta(name=name, is_active=True) @@ -92,29 +95,27 @@ class ExampleLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the step of the form.""" errors = {} if user_input is not None: try: - cast(ExampleAuthProvider, self._auth_provider)\ - .async_validate_login(user_input['username'], - user_input['password']) + cast(ExampleAuthProvider, self._auth_provider).async_validate_login( + user_input["username"], user_input["password"] + ) except InvalidAuthError: - errors['base'] = 'invalid_auth' + errors["base"] = "invalid_auth" if not errors: - user_input.pop('password') + user_input.pop("password") return await self.async_finish(user_input) schema = OrderedDict() # type: Dict[str, type] - schema['username'] = str - schema['password'] = str + schema["username"] = str + schema["password"] = str return self.async_show_form( - step_id='init', - data_schema=vol.Schema(schema), - errors=errors, + step_id="init", data_schema=vol.Schema(schema), errors=errors ) diff --git a/homeassistant/auth/providers/legacy_api_password.py b/homeassistant/auth/providers/legacy_api_password.py index 111b9e7d3..c8a146d9c 100644 --- a/homeassistant/auth/providers/legacy_api_password.py +++ b/homeassistant/auth/providers/legacy_api_password.py @@ -16,26 +16,23 @@ from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow from ..models import Credentials, UserMeta -USER_SCHEMA = vol.Schema({ - vol.Required('username'): str, -}) +USER_SCHEMA = vol.Schema({vol.Required("username"): str}) -CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({ -}, extra=vol.PREVENT_EXTRA) +CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({}, extra=vol.PREVENT_EXTRA) -LEGACY_USER_NAME = 'Legacy API password user' +LEGACY_USER_NAME = "Legacy API password user" class InvalidAuthError(HomeAssistantError): """Raised when submitting invalid authentication.""" -@AUTH_PROVIDERS.register('legacy_api_password') +@AUTH_PROVIDERS.register("legacy_api_password") class LegacyApiPasswordAuthProvider(AuthProvider): """Example auth provider based on hardcoded usernames and passwords.""" - DEFAULT_TITLE = 'Legacy API Password' + DEFAULT_TITLE = "Legacy API Password" async def async_login_flow(self, context: Optional[Dict]) -> LoginFlow: """Return a flow to login.""" @@ -44,14 +41,16 @@ class LegacyApiPasswordAuthProvider(AuthProvider): @callback def async_validate_login(self, password: str) -> None: """Validate a username and password.""" - hass_http = getattr(self.hass, 'http', None) # type: HomeAssistantHTTP + hass_http = getattr(self.hass, "http", None) # type: HomeAssistantHTTP - if not hmac.compare_digest(hass_http.api_password.encode('utf-8'), - password.encode('utf-8')): + if not hmac.compare_digest( + hass_http.api_password.encode("utf-8"), password.encode("utf-8") + ): raise InvalidAuthError async def async_get_or_create_credentials( - self, flow_result: Dict[str, str]) -> Credentials: + self, flow_result: Dict[str, str] + ) -> Credentials: """Return credentials for this login.""" credentials = await self.async_credentials() if credentials: @@ -60,7 +59,8 @@ class LegacyApiPasswordAuthProvider(AuthProvider): return self.async_create_credentials({}) async def async_user_meta_for_credentials( - self, credentials: Credentials) -> UserMeta: + self, credentials: Credentials + ) -> UserMeta: """ Return info for the user. @@ -73,29 +73,26 @@ class LegacyLoginFlow(LoginFlow): """Handler for the login flow.""" async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the step of the form.""" errors = {} - hass_http = getattr(self.hass, 'http', None) + hass_http = getattr(self.hass, "http", None) if hass_http is None or not hass_http.api_password: - return self.async_abort( - reason='no_api_password_set' - ) + return self.async_abort(reason="no_api_password_set") if user_input is not None: try: - cast(LegacyApiPasswordAuthProvider, self._auth_provider)\ - .async_validate_login(user_input['password']) + cast( + LegacyApiPasswordAuthProvider, self._auth_provider + ).async_validate_login(user_input["password"]) except InvalidAuthError: - errors['base'] = 'invalid_auth' + errors["base"] = "invalid_auth" if not errors: return await self.async_finish({}) return self.async_show_form( - step_id='init', - data_schema=vol.Schema({'password': str}), - errors=errors, + step_id="init", data_schema=vol.Schema({"password": str}), errors=errors ) diff --git a/homeassistant/auth/providers/trusted_networks.py b/homeassistant/auth/providers/trusted_networks.py index 8a7e1d67c..559792485 100644 --- a/homeassistant/auth/providers/trusted_networks.py +++ b/homeassistant/auth/providers/trusted_networks.py @@ -14,8 +14,7 @@ from homeassistant.exceptions import HomeAssistantError from . import AuthProvider, AUTH_PROVIDER_SCHEMA, AUTH_PROVIDERS, LoginFlow from ..models import Credentials, UserMeta -CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({ -}, extra=vol.PREVENT_EXTRA) +CONFIG_SCHEMA = AUTH_PROVIDER_SCHEMA.extend({}, extra=vol.PREVENT_EXTRA) class InvalidAuthError(HomeAssistantError): @@ -26,14 +25,14 @@ class InvalidUserError(HomeAssistantError): """Raised when try to login as invalid user.""" -@AUTH_PROVIDERS.register('trusted_networks') +@AUTH_PROVIDERS.register("trusted_networks") class TrustedNetworksAuthProvider(AuthProvider): """Trusted Networks auth provider. Allow passwordless access from trusted network. """ - DEFAULT_TITLE = 'Trusted Networks' + DEFAULT_TITLE = "Trusted Networks" @property def support_mfa(self) -> bool: @@ -44,27 +43,29 @@ class TrustedNetworksAuthProvider(AuthProvider): """Return a flow to login.""" assert context is not None users = await self.store.async_get_users() - available_users = {user.id: user.name - for user in users - if not user.system_generated and user.is_active} + available_users = { + user.id: user.name + for user in users + if not user.system_generated and user.is_active + } return TrustedNetworksLoginFlow( - self, cast(str, context.get('ip_address')), available_users) + self, cast(str, context.get("ip_address")), available_users + ) async def async_get_or_create_credentials( - self, flow_result: Dict[str, str]) -> Credentials: + self, flow_result: Dict[str, str] + ) -> Credentials: """Get credentials based on the flow result.""" - user_id = flow_result['user'] + user_id = flow_result["user"] users = await self.store.async_get_users() for user in users: - if (not user.system_generated and - user.is_active and - user.id == user_id): + if not user.system_generated and user.is_active and user.id == user_id: for credential in await self.async_credentials(): - if credential.data['user_id'] == user_id: + if credential.data["user_id"] == user_id: return credential - cred = self.async_create_credentials({'user_id': user_id}) + cred = self.async_create_credentials({"user_id": user_id}) await self.store.async_link_user(user, cred) return cred @@ -72,7 +73,8 @@ class TrustedNetworksAuthProvider(AuthProvider): raise InvalidUserError async def async_user_meta_for_credentials( - self, credentials: Credentials) -> UserMeta: + self, credentials: Credentials + ) -> UserMeta: """Return extra user metadata for credentials. Trusted network auth provider should never create new user. @@ -86,44 +88,48 @@ class TrustedNetworksAuthProvider(AuthProvider): Raise InvalidAuthError if not. Raise InvalidAuthError if trusted_networks is not configured. """ - hass_http = getattr(self.hass, 'http', None) # type: HomeAssistantHTTP + hass_http = getattr(self.hass, "http", None) # type: HomeAssistantHTTP if not hass_http or not hass_http.trusted_networks: - raise InvalidAuthError('trusted_networks is not configured') + raise InvalidAuthError("trusted_networks is not configured") - if not any(ip_address in trusted_network for trusted_network - in hass_http.trusted_networks): - raise InvalidAuthError('Not in trusted_networks') + if not any( + ip_address in trusted_network + for trusted_network in hass_http.trusted_networks + ): + raise InvalidAuthError("Not in trusted_networks") class TrustedNetworksLoginFlow(LoginFlow): """Handler for the login flow.""" - def __init__(self, auth_provider: TrustedNetworksAuthProvider, - ip_address: str, available_users: Dict[str, Optional[str]]) \ - -> None: + def __init__( + self, + auth_provider: TrustedNetworksAuthProvider, + ip_address: str, + available_users: Dict[str, Optional[str]], + ) -> None: """Initialize the login flow.""" super().__init__(auth_provider) self._available_users = available_users self._ip_address = ip_address async def async_step_init( - self, user_input: Optional[Dict[str, str]] = None) \ - -> Dict[str, Any]: + self, user_input: Optional[Dict[str, str]] = None + ) -> Dict[str, Any]: """Handle the step of the form.""" try: - cast(TrustedNetworksAuthProvider, self._auth_provider)\ - .async_validate_access(self._ip_address) + cast( + TrustedNetworksAuthProvider, self._auth_provider + ).async_validate_access(self._ip_address) except InvalidAuthError: - return self.async_abort( - reason='not_whitelisted' - ) + return self.async_abort(reason="not_whitelisted") if user_input is not None: return await self.async_finish(user_input) return self.async_show_form( - step_id='init', - data_schema=vol.Schema({'user': vol.In(self._available_users)}), + step_id="init", + data_schema=vol.Schema({"user": vol.In(self._available_users)}), ) diff --git a/homeassistant/auth/util.py b/homeassistant/auth/util.py index 402caae46..83834fa76 100644 --- a/homeassistant/auth/util.py +++ b/homeassistant/auth/util.py @@ -10,4 +10,4 @@ def generate_secret(entropy: int = 32) -> str: Event loop friendly. """ - return binascii.hexlify(os.urandom(entropy)).decode('ascii') + return binascii.hexlify(os.urandom(entropy)).decode("ascii") diff --git a/homeassistant/bootstrap.py b/homeassistant/bootstrap.py index 2125ab46a..19c438b87 100644 --- a/homeassistant/bootstrap.py +++ b/homeassistant/bootstrap.py @@ -10,7 +10,11 @@ from typing import Any, Optional, Dict import voluptuous as vol from homeassistant import ( - core, config as conf_util, config_entries, components as core_components) + core, + config as conf_util, + config_entries, + components as core_components, +) from homeassistant.components import persistent_notification from homeassistant.const import EVENT_HOMEASSISTANT_CLOSE from homeassistant.setup import async_setup_component @@ -22,25 +26,34 @@ from homeassistant.helpers.signal import async_register_signal_handling _LOGGER = logging.getLogger(__name__) -ERROR_LOG_FILENAME = 'home-assistant.log' +ERROR_LOG_FILENAME = "home-assistant.log" # hass.data key for logging information. -DATA_LOGGING = 'logging' +DATA_LOGGING = "logging" -FIRST_INIT_COMPONENT = {'system_log', 'recorder', 'mqtt', 'mqtt_eventstream', - 'logger', 'introduction', 'frontend', 'history'} +FIRST_INIT_COMPONENT = { + "system_log", + "recorder", + "mqtt", + "mqtt_eventstream", + "logger", + "introduction", + "frontend", + "history", +} -def from_config_dict(config: Dict[str, Any], - hass: Optional[core.HomeAssistant] = None, - config_dir: Optional[str] = None, - enable_log: bool = True, - verbose: bool = False, - skip_pip: bool = False, - log_rotate_days: Any = None, - log_file: Any = None, - log_no_color: bool = False) \ - -> Optional[core.HomeAssistant]: +def from_config_dict( + config: Dict[str, Any], + hass: Optional[core.HomeAssistant] = None, + config_dir: Optional[str] = None, + enable_log: bool = True, + verbose: bool = False, + skip_pip: bool = False, + log_rotate_days: Any = None, + log_file: Any = None, + log_no_color: bool = False, +) -> Optional[core.HomeAssistant]: """Try to configure Home Assistant from a configuration dictionary. Dynamically loads required components and its dependencies. @@ -51,28 +64,36 @@ def from_config_dict(config: Dict[str, Any], config_dir = os.path.abspath(config_dir) hass.config.config_dir = config_dir if not is_virtual_env(): - hass.loop.run_until_complete( - async_mount_local_lib_path(config_dir)) + hass.loop.run_until_complete(async_mount_local_lib_path(config_dir)) # run task hass = hass.loop.run_until_complete( async_from_config_dict( - config, hass, config_dir, enable_log, verbose, skip_pip, - log_rotate_days, log_file, log_no_color) + config, + hass, + config_dir, + enable_log, + verbose, + skip_pip, + log_rotate_days, + log_file, + log_no_color, + ) ) return hass -async def async_from_config_dict(config: Dict[str, Any], - hass: core.HomeAssistant, - config_dir: Optional[str] = None, - enable_log: bool = True, - verbose: bool = False, - skip_pip: bool = False, - log_rotate_days: Any = None, - log_file: Any = None, - log_no_color: bool = False) \ - -> Optional[core.HomeAssistant]: +async def async_from_config_dict( + config: Dict[str, Any], + hass: core.HomeAssistant, + config_dir: Optional[str] = None, + enable_log: bool = True, + verbose: bool = False, + skip_pip: bool = False, + log_rotate_days: Any = None, + log_file: Any = None, + log_no_color: bool = False, +) -> Optional[core.HomeAssistant]: """Try to configure Home Assistant from a configuration dictionary. Dynamically loads required components and its dependencies. @@ -81,40 +102,41 @@ async def async_from_config_dict(config: Dict[str, Any], start = time() if enable_log: - async_enable_logging(hass, verbose, log_rotate_days, log_file, - log_no_color) + async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color) core_config = config.get(core.DOMAIN, {}) - has_api_password = bool((config.get('http') or {}).get('api_password')) - has_trusted_networks = bool((config.get('http') or {}) - .get('trusted_networks')) + has_api_password = bool((config.get("http") or {}).get("api_password")) + has_trusted_networks = bool((config.get("http") or {}).get("trusted_networks")) try: await conf_util.async_process_ha_core_config( - hass, core_config, has_api_password, has_trusted_networks) + hass, core_config, has_api_password, has_trusted_networks + ) except vol.Invalid as config_err: - conf_util.async_log_exception( - config_err, 'homeassistant', core_config, hass) + conf_util.async_log_exception(config_err, "homeassistant", core_config, hass) return None except HomeAssistantError: - _LOGGER.error("Home Assistant core failed to initialize. " - "Further initialization aborted") + _LOGGER.error( + "Home Assistant core failed to initialize. " + "Further initialization aborted" + ) return None - await hass.async_add_executor_job( - conf_util.process_ha_config_upgrade, hass) + await hass.async_add_executor_job(conf_util.process_ha_config_upgrade, hass) hass.config.skip_pip = skip_pip if skip_pip: - _LOGGER.warning("Skipping pip installation of required modules. " - "This may cause issues") + _LOGGER.warning( + "Skipping pip installation of required modules. " "This may cause issues" + ) # Make a copy because we are mutating it. config = OrderedDict(config) # Merge packages conf_util.merge_packages_config( - hass, config, core_config.get(conf_util.CONF_PACKAGES, {})) + hass, config, core_config.get(conf_util.CONF_PACKAGES, {}) + ) # Ensure we have no None values after merge for key, value in config.items(): @@ -125,15 +147,16 @@ async def async_from_config_dict(config: Dict[str, Any], await hass.config_entries.async_load() # Filter out the repeating and common config section [homeassistant] - components = set(key.split(' ')[0] for key in config.keys() - if key != core.DOMAIN) + components = set(key.split(" ")[0] for key in config.keys() if key != core.DOMAIN) components.update(hass.config_entries.async_domains()) # setup components res = await core_components.async_setup(hass, config) if not res: - _LOGGER.error("Home Assistant core failed to initialize. " - "Further initialization aborted") + _LOGGER.error( + "Home Assistant core failed to initialize. " + "Further initialization aborted" + ) return hass await persistent_notification.async_setup(hass, config) @@ -157,20 +180,21 @@ async def async_from_config_dict(config: Dict[str, Any], await hass.async_block_till_done() stop = time() - _LOGGER.info("Home Assistant initialized in %.2fs", stop-start) + _LOGGER.info("Home Assistant initialized in %.2fs", stop - start) async_register_signal_handling(hass) return hass -def from_config_file(config_path: str, - hass: Optional[core.HomeAssistant] = None, - verbose: bool = False, - skip_pip: bool = True, - log_rotate_days: Any = None, - log_file: Any = None, - log_no_color: bool = False)\ - -> Optional[core.HomeAssistant]: +def from_config_file( + config_path: str, + hass: Optional[core.HomeAssistant] = None, + verbose: bool = False, + skip_pip: bool = True, + log_rotate_days: Any = None, + log_file: Any = None, + log_no_color: bool = False, +) -> Optional[core.HomeAssistant]: """Read the configuration file and try to start all the functionality. Will add functionality to 'hass' parameter if given, @@ -182,21 +206,28 @@ def from_config_file(config_path: str, # run task hass = hass.loop.run_until_complete( async_from_config_file( - config_path, hass, verbose, skip_pip, - log_rotate_days, log_file, log_no_color) + config_path, + hass, + verbose, + skip_pip, + log_rotate_days, + log_file, + log_no_color, + ) ) return hass -async def async_from_config_file(config_path: str, - hass: core.HomeAssistant, - verbose: bool = False, - skip_pip: bool = True, - log_rotate_days: Any = None, - log_file: Any = None, - log_no_color: bool = False)\ - -> Optional[core.HomeAssistant]: +async def async_from_config_file( + config_path: str, + hass: core.HomeAssistant, + verbose: bool = False, + skip_pip: bool = True, + log_rotate_days: Any = None, + log_file: Any = None, + log_no_color: bool = False, +) -> Optional[core.HomeAssistant]: """Read the configuration file and try to start all the functionality. Will add functionality to 'hass' parameter. @@ -209,12 +240,12 @@ async def async_from_config_file(config_path: str, if not is_virtual_env(): await async_mount_local_lib_path(config_dir) - async_enable_logging(hass, verbose, log_rotate_days, log_file, - log_no_color) + async_enable_logging(hass, verbose, log_rotate_days, log_file, log_no_color) try: config_dict = await hass.async_add_executor_job( - conf_util.load_yaml_config_file, config_path) + conf_util.load_yaml_config_file, config_path + ) except HomeAssistantError as err: _LOGGER.error("Error loading %s: %s", config_path, err) return None @@ -222,43 +253,48 @@ async def async_from_config_file(config_path: str, clear_secret_cache() return await async_from_config_dict( - config_dict, hass, enable_log=False, skip_pip=skip_pip) + config_dict, hass, enable_log=False, skip_pip=skip_pip + ) @core.callback -def async_enable_logging(hass: core.HomeAssistant, - verbose: bool = False, - log_rotate_days: Optional[int] = None, - log_file: Optional[str] = None, - log_no_color: bool = False) -> None: +def async_enable_logging( + hass: core.HomeAssistant, + verbose: bool = False, + log_rotate_days: Optional[int] = None, + log_file: Optional[str] = None, + log_no_color: bool = False, +) -> None: """Set up the logging. This method must be run in the event loop. """ - fmt = ("%(asctime)s %(levelname)s (%(threadName)s) " - "[%(name)s] %(message)s") - datefmt = '%Y-%m-%d %H:%M:%S' + fmt = "%(asctime)s %(levelname)s (%(threadName)s) " "[%(name)s] %(message)s" + datefmt = "%Y-%m-%d %H:%M:%S" if not log_no_color: try: from colorlog import ColoredFormatter + # basicConfig must be called after importing colorlog in order to # ensure that the handlers it sets up wraps the correct streams. logging.basicConfig(level=logging.INFO) colorfmt = "%(log_color)s{}%(reset)s".format(fmt) - logging.getLogger().handlers[0].setFormatter(ColoredFormatter( - colorfmt, - datefmt=datefmt, - reset=True, - log_colors={ - 'DEBUG': 'cyan', - 'INFO': 'green', - 'WARNING': 'yellow', - 'ERROR': 'red', - 'CRITICAL': 'red', - } - )) + logging.getLogger().handlers[0].setFormatter( + ColoredFormatter( + colorfmt, + datefmt=datefmt, + reset=True, + log_colors={ + "DEBUG": "cyan", + "INFO": "green", + "WARNING": "yellow", + "ERROR": "red", + "CRITICAL": "red", + }, + ) + ) except ImportError: pass @@ -267,9 +303,9 @@ def async_enable_logging(hass: core.HomeAssistant, logging.basicConfig(format=fmt, datefmt=datefmt, level=logging.INFO) # Suppress overly verbose logs from libraries that aren't helpful - logging.getLogger('requests').setLevel(logging.WARNING) - logging.getLogger('urllib3').setLevel(logging.WARNING) - logging.getLogger('aiohttp.access').setLevel(logging.WARNING) + logging.getLogger("requests").setLevel(logging.WARNING) + logging.getLogger("urllib3").setLevel(logging.WARNING) + logging.getLogger("aiohttp.access").setLevel(logging.WARNING) # Log errors to a file if we have write access to file or config dir if log_file is None: @@ -282,16 +318,16 @@ def async_enable_logging(hass: core.HomeAssistant, # Check if we can write to the error log if it exists or that # we can create files in the containing directory if not. - if (err_path_exists and os.access(err_log_path, os.W_OK)) or \ - (not err_path_exists and os.access(err_dir, os.W_OK)): + if (err_path_exists and os.access(err_log_path, os.W_OK)) or ( + not err_path_exists and os.access(err_dir, os.W_OK) + ): if log_rotate_days: err_handler = logging.handlers.TimedRotatingFileHandler( - err_log_path, when='midnight', - backupCount=log_rotate_days) # type: logging.FileHandler + err_log_path, when="midnight", backupCount=log_rotate_days + ) # type: logging.FileHandler else: - err_handler = logging.FileHandler( - err_log_path, mode='w', delay=True) + err_handler = logging.FileHandler(err_log_path, mode="w", delay=True) err_handler.setLevel(logging.INFO if verbose else logging.WARNING) err_handler.setFormatter(logging.Formatter(fmt, datefmt=datefmt)) @@ -300,21 +336,19 @@ def async_enable_logging(hass: core.HomeAssistant, async def async_stop_async_handler(_: Any) -> None: """Cleanup async handler.""" - logging.getLogger('').removeHandler(async_handler) # type: ignore + logging.getLogger("").removeHandler(async_handler) # type: ignore await async_handler.async_close(blocking=True) - hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_CLOSE, async_stop_async_handler) + hass.bus.async_listen_once(EVENT_HOMEASSISTANT_CLOSE, async_stop_async_handler) - logger = logging.getLogger('') + logger = logging.getLogger("") logger.addHandler(async_handler) # type: ignore logger.setLevel(logging.INFO) # Save the log file location for access by other components. hass.data[DATA_LOGGING] = err_log_path else: - _LOGGER.error( - "Unable to set up error log %s (access denied)", err_log_path) + _LOGGER.error("Unable to set up error log %s (access denied)", err_log_path) async def async_mount_local_lib_path(config_dir: str) -> str: @@ -322,7 +356,7 @@ async def async_mount_local_lib_path(config_dir: str) -> str: This function is a coroutine. """ - deps_dir = os.path.join(config_dir, 'deps') + deps_dir = os.path.join(config_dir, "deps") lib_dir = await async_get_user_site(deps_dir) if lib_dir not in sys.path: sys.path.insert(0, lib_dir) diff --git a/homeassistant/components/__init__.py b/homeassistant/components/__init__.py index bf1577cbf..3b491a7f3 100644 --- a/homeassistant/components/__init__.py +++ b/homeassistant/components/__init__.py @@ -18,14 +18,19 @@ from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers.service import extract_entity_ids from homeassistant.helpers import intent from homeassistant.const import ( - ATTR_ENTITY_ID, SERVICE_TURN_ON, SERVICE_TURN_OFF, SERVICE_TOGGLE, - SERVICE_HOMEASSISTANT_STOP, SERVICE_HOMEASSISTANT_RESTART, - RESTART_EXIT_CODE) + ATTR_ENTITY_ID, + SERVICE_TURN_ON, + SERVICE_TURN_OFF, + SERVICE_TOGGLE, + SERVICE_HOMEASSISTANT_STOP, + SERVICE_HOMEASSISTANT_RESTART, + RESTART_EXIT_CODE, +) _LOGGER = logging.getLogger(__name__) -SERVICE_RELOAD_CORE_CONFIG = 'reload_core_config' -SERVICE_CHECK_CONFIG = 'check_config' +SERVICE_RELOAD_CORE_CONFIG = "reload_core_config" +SERVICE_CHECK_CONFIG = "check_config" def is_on(hass, entity_id=None): @@ -45,11 +50,10 @@ def is_on(hass, entity_id=None): component = getattr(hass.components, domain) except ImportError: - _LOGGER.error('Failed to call %s.is_on: component not found', - domain) + _LOGGER.error("Failed to call %s.is_on: component not found", domain) continue - if not hasattr(component, 'is_on'): + if not hasattr(component, "is_on"): _LOGGER.warning("Component %s has no is_on method.", domain) continue @@ -112,6 +116,7 @@ def async_reload_core_config(hass): @asyncio.coroutine def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: """Set up general services related to Home Assistant.""" + @asyncio.coroutine def async_handle_turn_service(service): """Handle calls to homeassistant.turn_on/off.""" @@ -120,13 +125,14 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: # Generic turn on/off method requires entity id if not entity_ids: _LOGGER.error( - "homeassistant/%s cannot be called without entity_id", - service.service) + "homeassistant/%s cannot be called without entity_id", service.service + ) return # Group entity_ids by domain. groupby requires sorted data. - by_domain = it.groupby(sorted(entity_ids), - lambda item: ha.split_entity_id(item)[0]) + by_domain = it.groupby( + sorted(entity_ids), lambda item: ha.split_entity_id(item)[0] + ) tasks = [] @@ -145,24 +151,30 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: # ent_ids is a generator, convert it to a list. data[ATTR_ENTITY_ID] = list(ent_ids) - tasks.append(hass.services.async_call( - domain, service.service, data, blocking)) + tasks.append( + hass.services.async_call(domain, service.service, data, blocking) + ) yield from asyncio.wait(tasks, loop=hass.loop) - hass.services.async_register( - ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service) - hass.services.async_register( - ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service) - hass.services.async_register( - ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service) - hass.helpers.intent.async_register(intent.ServiceIntentHandler( - intent.INTENT_TURN_ON, ha.DOMAIN, SERVICE_TURN_ON, "Turned {} on")) - hass.helpers.intent.async_register(intent.ServiceIntentHandler( - intent.INTENT_TURN_OFF, ha.DOMAIN, SERVICE_TURN_OFF, - "Turned {} off")) - hass.helpers.intent.async_register(intent.ServiceIntentHandler( - intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}")) + hass.services.async_register(ha.DOMAIN, SERVICE_TURN_OFF, async_handle_turn_service) + hass.services.async_register(ha.DOMAIN, SERVICE_TURN_ON, async_handle_turn_service) + hass.services.async_register(ha.DOMAIN, SERVICE_TOGGLE, async_handle_turn_service) + hass.helpers.intent.async_register( + intent.ServiceIntentHandler( + intent.INTENT_TURN_ON, ha.DOMAIN, SERVICE_TURN_ON, "Turned {} on" + ) + ) + hass.helpers.intent.async_register( + intent.ServiceIntentHandler( + intent.INTENT_TURN_OFF, ha.DOMAIN, SERVICE_TURN_OFF, "Turned {} off" + ) + ) + hass.helpers.intent.async_register( + intent.ServiceIntentHandler( + intent.INTENT_TOGGLE, ha.DOMAIN, SERVICE_TOGGLE, "Toggled {}" + ) + ) @asyncio.coroutine def async_handle_core_service(call): @@ -180,18 +192,23 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: _LOGGER.error(errors) hass.components.persistent_notification.async_create( "Config error. See dev-info panel for details.", - "Config validating", "{0}.check_config".format(ha.DOMAIN)) + "Config validating", + "{0}.check_config".format(ha.DOMAIN), + ) return if call.service == SERVICE_HOMEASSISTANT_RESTART: hass.async_create_task(hass.async_stop(RESTART_EXIT_CODE)) hass.services.async_register( - ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service) + ha.DOMAIN, SERVICE_HOMEASSISTANT_STOP, async_handle_core_service + ) hass.services.async_register( - ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service) + ha.DOMAIN, SERVICE_HOMEASSISTANT_RESTART, async_handle_core_service + ) hass.services.async_register( - ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service) + ha.DOMAIN, SERVICE_CHECK_CONFIG, async_handle_core_service + ) @asyncio.coroutine def async_handle_reload_config(call): @@ -203,9 +220,11 @@ def async_setup(hass: ha.HomeAssistant, config: dict) -> Awaitable[bool]: return yield from conf_util.async_process_ha_core_config( - hass, conf.get(ha.DOMAIN) or {}) + hass, conf.get(ha.DOMAIN) or {} + ) hass.services.async_register( - ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config) + ha.DOMAIN, SERVICE_RELOAD_CORE_CONFIG, async_handle_reload_config + ) return True diff --git a/homeassistant/components/abode.py b/homeassistant/components/abode.py index bafbc0781..67eba8134 100644 --- a/homeassistant/components/abode.py +++ b/homeassistant/components/abode.py @@ -12,89 +12,109 @@ from requests.exceptions import HTTPError, ConnectTimeout import voluptuous as vol from homeassistant.const import ( - ATTR_ATTRIBUTION, ATTR_DATE, ATTR_TIME, ATTR_ENTITY_ID, CONF_USERNAME, - CONF_PASSWORD, CONF_EXCLUDE, CONF_NAME, CONF_LIGHTS, - EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START) + ATTR_ATTRIBUTION, + ATTR_DATE, + ATTR_TIME, + ATTR_ENTITY_ID, + CONF_USERNAME, + CONF_PASSWORD, + CONF_EXCLUDE, + CONF_NAME, + CONF_LIGHTS, + EVENT_HOMEASSISTANT_STOP, + EVENT_HOMEASSISTANT_START, +) from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery from homeassistant.helpers.entity import Entity -REQUIREMENTS = ['abodepy==0.13.1'] +REQUIREMENTS = ["abodepy==0.13.1"] _LOGGER = logging.getLogger(__name__) CONF_ATTRIBUTION = "Data provided by goabode.com" -CONF_POLLING = 'polling' +CONF_POLLING = "polling" -DOMAIN = 'abode' -DEFAULT_CACHEDB = './abodepy_cache.pickle' +DOMAIN = "abode" +DEFAULT_CACHEDB = "./abodepy_cache.pickle" -NOTIFICATION_ID = 'abode_notification' -NOTIFICATION_TITLE = 'Abode Security Setup' +NOTIFICATION_ID = "abode_notification" +NOTIFICATION_TITLE = "Abode Security Setup" -EVENT_ABODE_ALARM = 'abode_alarm' -EVENT_ABODE_ALARM_END = 'abode_alarm_end' -EVENT_ABODE_AUTOMATION = 'abode_automation' -EVENT_ABODE_FAULT = 'abode_panel_fault' -EVENT_ABODE_RESTORE = 'abode_panel_restore' +EVENT_ABODE_ALARM = "abode_alarm" +EVENT_ABODE_ALARM_END = "abode_alarm_end" +EVENT_ABODE_AUTOMATION = "abode_automation" +EVENT_ABODE_FAULT = "abode_panel_fault" +EVENT_ABODE_RESTORE = "abode_panel_restore" -SERVICE_SETTINGS = 'change_setting' -SERVICE_CAPTURE_IMAGE = 'capture_image' -SERVICE_TRIGGER = 'trigger_quick_action' +SERVICE_SETTINGS = "change_setting" +SERVICE_CAPTURE_IMAGE = "capture_image" +SERVICE_TRIGGER = "trigger_quick_action" -ATTR_DEVICE_ID = 'device_id' -ATTR_DEVICE_NAME = 'device_name' -ATTR_DEVICE_TYPE = 'device_type' -ATTR_EVENT_CODE = 'event_code' -ATTR_EVENT_NAME = 'event_name' -ATTR_EVENT_TYPE = 'event_type' -ATTR_EVENT_UTC = 'event_utc' -ATTR_SETTING = 'setting' -ATTR_USER_NAME = 'user_name' -ATTR_VALUE = 'value' +ATTR_DEVICE_ID = "device_id" +ATTR_DEVICE_NAME = "device_name" +ATTR_DEVICE_TYPE = "device_type" +ATTR_EVENT_CODE = "event_code" +ATTR_EVENT_NAME = "event_name" +ATTR_EVENT_TYPE = "event_type" +ATTR_EVENT_UTC = "event_utc" +ATTR_SETTING = "setting" +ATTR_USER_NAME = "user_name" +ATTR_VALUE = "value" ABODE_DEVICE_ID_LIST_SCHEMA = vol.Schema([str]) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_POLLING, default=False): cv.boolean, - vol.Optional(CONF_EXCLUDE, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA, - vol.Optional(CONF_LIGHTS, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_POLLING, default=False): cv.boolean, + vol.Optional(CONF_EXCLUDE, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA, + vol.Optional(CONF_LIGHTS, default=[]): ABODE_DEVICE_ID_LIST_SCHEMA, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -CHANGE_SETTING_SCHEMA = vol.Schema({ - vol.Required(ATTR_SETTING): cv.string, - vol.Required(ATTR_VALUE): cv.string -}) +CHANGE_SETTING_SCHEMA = vol.Schema( + {vol.Required(ATTR_SETTING): cv.string, vol.Required(ATTR_VALUE): cv.string} +) -CAPTURE_IMAGE_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids, -}) +CAPTURE_IMAGE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) -TRIGGER_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids, -}) +TRIGGER_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) ABODE_PLATFORMS = [ - 'alarm_control_panel', 'binary_sensor', 'lock', 'switch', 'cover', - 'camera', 'light', 'sensor' + "alarm_control_panel", + "binary_sensor", + "lock", + "switch", + "cover", + "camera", + "light", + "sensor", ] class AbodeSystem: """Abode System class.""" - def __init__(self, username, password, cache, - name, polling, exclude, lights): + def __init__(self, username, password, cache, name, polling, exclude, lights): """Initialize the system.""" import abodepy + self.abode = abodepy.Abode( - username, password, auto_login=True, get_devices=True, - get_automations=True, cache_path=cache) + username, + password, + auto_login=True, + get_devices=True, + get_automations=True, + cache_path=cache, + ) self.name = name self.polling = polling self.exclude = exclude @@ -113,9 +133,9 @@ class AbodeSystem: """Check if a switch device is configured as a light.""" import abodepy.helpers.constants as CONST - return (device.generic_type == CONST.TYPE_LIGHT or - (device.generic_type == CONST.TYPE_SWITCH and - device.device_id in self.lights)) + return device.generic_type == CONST.TYPE_LIGHT or ( + device.generic_type == CONST.TYPE_SWITCH and device.device_id in self.lights + ) def setup(hass, config): @@ -133,16 +153,18 @@ def setup(hass, config): try: cache = hass.config.path(DEFAULT_CACHEDB) hass.data[DOMAIN] = AbodeSystem( - username, password, cache, name, polling, exclude, lights) + username, password, cache, name, polling, exclude, lights + ) except (AbodeException, ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Abode: %s", str(ex)) hass.components.persistent_notification.create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) return False setup_hass_services(hass) @@ -173,8 +195,11 @@ def setup_hass_services(hass): """Capture a new image.""" entity_ids = call.data.get(ATTR_ENTITY_ID) - target_devices = [device for device in hass.data[DOMAIN].devices - if device.entity_id in entity_ids] + target_devices = [ + device + for device in hass.data[DOMAIN].devices + if device.entity_id in entity_ids + ] for device in target_devices: device.capture() @@ -183,27 +208,31 @@ def setup_hass_services(hass): """Trigger a quick action.""" entity_ids = call.data.get(ATTR_ENTITY_ID, None) - target_devices = [device for device in hass.data[DOMAIN].devices - if device.entity_id in entity_ids] + target_devices = [ + device + for device in hass.data[DOMAIN].devices + if device.entity_id in entity_ids + ] for device in target_devices: device.trigger() hass.services.register( - DOMAIN, SERVICE_SETTINGS, change_setting, - schema=CHANGE_SETTING_SCHEMA) + DOMAIN, SERVICE_SETTINGS, change_setting, schema=CHANGE_SETTING_SCHEMA + ) hass.services.register( - DOMAIN, SERVICE_CAPTURE_IMAGE, capture_image, - schema=CAPTURE_IMAGE_SCHEMA) + DOMAIN, SERVICE_CAPTURE_IMAGE, capture_image, schema=CAPTURE_IMAGE_SCHEMA + ) hass.services.register( - DOMAIN, SERVICE_TRIGGER, trigger_quick_action, - schema=TRIGGER_SCHEMA) + DOMAIN, SERVICE_TRIGGER, trigger_quick_action, schema=TRIGGER_SCHEMA + ) def setup_hass_events(hass): """Home Assistant start and stop callbacks.""" + def startup(event): """Listen for push events.""" hass.data[DOMAIN].abode.events.start() @@ -229,28 +258,32 @@ def setup_abode_events(hass): def event_callback(event, event_json): """Handle an event callback from Abode.""" data = { - ATTR_DEVICE_ID: event_json.get(ATTR_DEVICE_ID, ''), - ATTR_DEVICE_NAME: event_json.get(ATTR_DEVICE_NAME, ''), - ATTR_DEVICE_TYPE: event_json.get(ATTR_DEVICE_TYPE, ''), - ATTR_EVENT_CODE: event_json.get(ATTR_EVENT_CODE, ''), - ATTR_EVENT_NAME: event_json.get(ATTR_EVENT_NAME, ''), - ATTR_EVENT_TYPE: event_json.get(ATTR_EVENT_TYPE, ''), - ATTR_EVENT_UTC: event_json.get(ATTR_EVENT_UTC, ''), - ATTR_USER_NAME: event_json.get(ATTR_USER_NAME, ''), - ATTR_DATE: event_json.get(ATTR_DATE, ''), - ATTR_TIME: event_json.get(ATTR_TIME, ''), + ATTR_DEVICE_ID: event_json.get(ATTR_DEVICE_ID, ""), + ATTR_DEVICE_NAME: event_json.get(ATTR_DEVICE_NAME, ""), + ATTR_DEVICE_TYPE: event_json.get(ATTR_DEVICE_TYPE, ""), + ATTR_EVENT_CODE: event_json.get(ATTR_EVENT_CODE, ""), + ATTR_EVENT_NAME: event_json.get(ATTR_EVENT_NAME, ""), + ATTR_EVENT_TYPE: event_json.get(ATTR_EVENT_TYPE, ""), + ATTR_EVENT_UTC: event_json.get(ATTR_EVENT_UTC, ""), + ATTR_USER_NAME: event_json.get(ATTR_USER_NAME, ""), + ATTR_DATE: event_json.get(ATTR_DATE, ""), + ATTR_TIME: event_json.get(ATTR_TIME, ""), } hass.bus.fire(event, data) - events = [TIMELINE.ALARM_GROUP, TIMELINE.ALARM_END_GROUP, - TIMELINE.PANEL_FAULT_GROUP, TIMELINE.PANEL_RESTORE_GROUP, - TIMELINE.AUTOMATION_GROUP] + events = [ + TIMELINE.ALARM_GROUP, + TIMELINE.ALARM_END_GROUP, + TIMELINE.PANEL_FAULT_GROUP, + TIMELINE.PANEL_RESTORE_GROUP, + TIMELINE.AUTOMATION_GROUP, + ] for event in events: hass.data[DOMAIN].abode.events.add_event_callback( - event, - partial(event_callback, event)) + event, partial(event_callback, event) + ) class AbodeDevice(Entity): @@ -266,7 +299,8 @@ class AbodeDevice(Entity): """Subscribe Abode events.""" self.hass.async_add_job( self._data.abode.events.add_device_callback, - self._device.device_id, self._update_callback + self._device.device_id, + self._update_callback, ) @property @@ -288,10 +322,10 @@ class AbodeDevice(Entity): """Return the state attributes.""" return { ATTR_ATTRIBUTION: CONF_ATTRIBUTION, - 'device_id': self._device.device_id, - 'battery_low': self._device.battery_low, - 'no_response': self._device.no_response, - 'device_type': self._device.type + "device_id": self._device.device_id, + "battery_low": self._device.battery_low, + "no_response": self._device.no_response, + "device_type": self._device.type, } def _update_callback(self, device): @@ -314,7 +348,8 @@ class AbodeAutomation(Entity): if self._event: self.hass.async_add_job( self._data.abode.events.add_event_callback, - self._event, self._update_callback + self._event, + self._update_callback, ) @property @@ -336,9 +371,9 @@ class AbodeAutomation(Entity): """Return the state attributes.""" return { ATTR_ATTRIBUTION: CONF_ATTRIBUTION, - 'automation_id': self._automation.automation_id, - 'type': self._automation.type, - 'sub_type': self._automation.sub_type + "automation_id": self._automation.automation_id, + "type": self._automation.type, + "sub_type": self._automation.sub_type, } def _update_callback(self, device): diff --git a/homeassistant/components/ads/__init__.py b/homeassistant/components/ads/__init__.py index 100444c02..f3960856a 100644 --- a/homeassistant/components/ads/__init__.py +++ b/homeassistant/components/ads/__init__.py @@ -10,51 +10,62 @@ import logging import ctypes from collections import namedtuple import voluptuous as vol -from homeassistant.const import CONF_DEVICE, CONF_PORT, CONF_IP_ADDRESS, \ - EVENT_HOMEASSISTANT_STOP +from homeassistant.const import ( + CONF_DEVICE, + CONF_PORT, + CONF_IP_ADDRESS, + EVENT_HOMEASSISTANT_STOP, +) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyads==2.2.6'] +REQUIREMENTS = ["pyads==2.2.6"] _LOGGER = logging.getLogger(__name__) -DATA_ADS = 'data_ads' +DATA_ADS = "data_ads" # Supported Types -ADSTYPE_INT = 'int' -ADSTYPE_UINT = 'uint' -ADSTYPE_BYTE = 'byte' -ADSTYPE_BOOL = 'bool' +ADSTYPE_INT = "int" +ADSTYPE_UINT = "uint" +ADSTYPE_BYTE = "byte" +ADSTYPE_BOOL = "bool" -DOMAIN = 'ads' +DOMAIN = "ads" -CONF_ADS_VAR = 'adsvar' -CONF_ADS_VAR_BRIGHTNESS = 'adsvar_brightness' -CONF_ADS_TYPE = 'adstype' -CONF_ADS_FACTOR = 'factor' -CONF_ADS_VALUE = 'value' +CONF_ADS_VAR = "adsvar" +CONF_ADS_VAR_BRIGHTNESS = "adsvar_brightness" +CONF_ADS_TYPE = "adstype" +CONF_ADS_FACTOR = "factor" +CONF_ADS_VALUE = "value" -SERVICE_WRITE_DATA_BY_NAME = 'write_data_by_name' +SERVICE_WRITE_DATA_BY_NAME = "write_data_by_name" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_DEVICE): cv.string, - vol.Required(CONF_PORT): cv.port, - vol.Optional(CONF_IP_ADDRESS): cv.string, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_DEVICE): cv.string, + vol.Required(CONF_PORT): cv.port, + vol.Optional(CONF_IP_ADDRESS): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema({ - vol.Required(CONF_ADS_TYPE): - vol.In([ADSTYPE_INT, ADSTYPE_UINT, ADSTYPE_BYTE]), - vol.Required(CONF_ADS_VALUE): cv.match_all, - vol.Required(CONF_ADS_VAR): cv.string, -}) +SCHEMA_SERVICE_WRITE_DATA_BY_NAME = vol.Schema( + { + vol.Required(CONF_ADS_TYPE): vol.In([ADSTYPE_INT, ADSTYPE_UINT, ADSTYPE_BYTE]), + vol.Required(CONF_ADS_VALUE): cv.match_all, + vol.Required(CONF_ADS_VAR): cv.string, + } +) def setup(hass, config): """Set up the ADS component.""" import pyads + conf = config[DOMAIN] net_id = conf.get(CONF_DEVICE) @@ -79,8 +90,7 @@ def setup(hass, config): try: ads = AdsHub(client) except pyads.pyads.ADSError: - _LOGGER.error( - "Could not connect to ADS host (netid=%s, port=%s)", net_id, port) + _LOGGER.error("Could not connect to ADS host (netid=%s, port=%s)", net_id, port) return False hass.data[DATA_ADS] = ads @@ -98,15 +108,18 @@ def setup(hass, config): _LOGGER.error(err) hass.services.register( - DOMAIN, SERVICE_WRITE_DATA_BY_NAME, handle_write_data_by_name, - schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME) + DOMAIN, + SERVICE_WRITE_DATA_BY_NAME, + handle_write_data_by_name, + schema=SCHEMA_SERVICE_WRITE_DATA_BY_NAME, + ) return True # Tuple to hold data needed for notification NotificationItem = namedtuple( - 'NotificationItem', 'hnotify huser name plc_datatype callback' + "NotificationItem", "hnotify huser name plc_datatype callback" ) @@ -128,12 +141,13 @@ class AdsHub: _LOGGER.debug("Shutting down ADS") for notification_item in self._notification_items.values(): self._client.del_device_notification( - notification_item.hnotify, - notification_item.huser + notification_item.hnotify, notification_item.huser ) _LOGGER.debug( "Deleting device notification %d, %d", - notification_item.hnotify, notification_item.huser) + notification_item.hnotify, + notification_item.huser, + ) self._client.close() def register_device(self, device): @@ -153,18 +167,20 @@ class AdsHub: def add_device_notification(self, name, plc_datatype, callback): """Add a notification to the ADS devices.""" from pyads import NotificationAttrib + attr = NotificationAttrib(ctypes.sizeof(plc_datatype)) with self._lock: hnotify, huser = self._client.add_device_notification( - name, attr, self._device_notification_callback) + name, attr, self._device_notification_callback + ) hnotify = int(hnotify) - _LOGGER.debug( - "Added device notification %d for variable %s", hnotify, name) + _LOGGER.debug("Added device notification %d for variable %s", hnotify, name) self._notification_items[hnotify] = NotificationItem( - hnotify, huser, name, plc_datatype, callback) + hnotify, huser, name, plc_datatype, callback + ) def _device_notification_callback(self, addr, notification, huser): """Handle device notifications.""" @@ -182,13 +198,13 @@ class AdsHub: # Parse data to desired datatype if notification_item.plc_datatype == self.PLCTYPE_BOOL: - value = bool(struct.unpack('= 1): + if ( + "resolutions" in request + and "resolutionsPerAuthority" in request["resolutions"] + and len(request["resolutions"]["resolutionsPerAuthority"]) >= 1 + ): # Extract all of the possible values from each authority with a # successful match possible_values = [] - for entry in request['resolutions']['resolutionsPerAuthority']: - if entry['status']['code'] != SYN_RESOLUTION_MATCH: + for entry in request["resolutions"]["resolutionsPerAuthority"]: + if entry["status"]["code"] != SYN_RESOLUTION_MATCH: continue - possible_values.extend([item['value']['name'] - for item - in entry['values']]) + possible_values.extend([item["value"]["name"] for item in entry["values"]]) # If there is only one match use the resolved value, otherwise the # resolution cannot be determined, so use the spoken slot value @@ -202,9 +209,9 @@ def resolve_slot_synonyms(key, request): resolved_value = possible_values[0] else: _LOGGER.debug( - 'Found multiple synonym resolutions for slot value: {%s: %s}', + "Found multiple synonym resolutions for slot value: {%s: %s}", key, - request['value'] + request["value"], ) return resolved_value @@ -225,12 +232,12 @@ class AlexaResponse: # Intent is None if request was a LaunchRequest or SessionEndedRequest if intent_info is not None: - for key, value in intent_info.get('slots', {}).items(): + for key, value in intent_info.get("slots", {}).items(): # Only include slots with values - if 'value' not in value: + if "value" not in value: continue - _key = key.replace('.', '_') + _key = key.replace(".", "_") self.variables[_key] = resolve_slot_synonyms(key, value) @@ -238,9 +245,7 @@ class AlexaResponse: """Add a card to the response.""" assert self.card is None - card = { - "type": card_type.value - } + card = {"type": card_type.value} if card_type == CardType.link_account: self.card = card @@ -254,43 +259,36 @@ class AlexaResponse: """Add speech to the response.""" assert self.speech is None - key = 'ssml' if speech_type == SpeechType.ssml else 'text' + key = "ssml" if speech_type == SpeechType.ssml else "text" - self.speech = { - 'type': speech_type.value, - key: text - } + self.speech = {"type": speech_type.value, key: text} def add_reprompt(self, speech_type, text): """Add reprompt if user does not answer.""" assert self.reprompt is None - key = 'ssml' if speech_type == SpeechType.ssml else 'text' + key = "ssml" if speech_type == SpeechType.ssml else "text" self.reprompt = { - 'type': speech_type.value, - key: text.async_render(self.variables) + "type": speech_type.value, + key: text.async_render(self.variables), } def as_dict(self): """Return response in an Alexa valid dict.""" - response = { - 'shouldEndSession': self.should_end_session - } + response = {"shouldEndSession": self.should_end_session} if self.card is not None: - response['card'] = self.card + response["card"] = self.card if self.speech is not None: - response['outputSpeech'] = self.speech + response["outputSpeech"] = self.speech if self.reprompt is not None: - response['reprompt'] = { - 'outputSpeech': self.reprompt - } + response["reprompt"] = {"outputSpeech": self.reprompt} return { - 'version': '1.0', - 'sessionAttributes': self.session_attributes, - 'response': response, + "version": "1.0", + "sessionAttributes": self.session_attributes, + "response": response, } diff --git a/homeassistant/components/alexa/smart_home.py b/homeassistant/components/alexa/smart_home.py index eab725c46..8b01179f7 100644 --- a/homeassistant/components/alexa/smart_home.py +++ b/homeassistant/components/alexa/smart_home.py @@ -6,55 +6,81 @@ from datetime import datetime from uuid import uuid4 from homeassistant.components import ( - alert, automation, cover, climate, fan, group, input_boolean, light, lock, - media_player, scene, script, switch, http, sensor) + alert, + automation, + cover, + climate, + fan, + group, + input_boolean, + light, + lock, + media_player, + scene, + script, + switch, + http, + sensor, +) import homeassistant.core as ha import homeassistant.util.color as color_util from homeassistant.util.temperature import convert as convert_temperature from homeassistant.util.decorator import Registry from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, ATTR_TEMPERATURE, - ATTR_UNIT_OF_MEASUREMENT, CONF_NAME, SERVICE_LOCK, - SERVICE_MEDIA_NEXT_TRACK, SERVICE_MEDIA_PAUSE, SERVICE_MEDIA_PLAY, - SERVICE_MEDIA_PREVIOUS_TRACK, SERVICE_MEDIA_STOP, - SERVICE_SET_COVER_POSITION, SERVICE_TURN_OFF, SERVICE_TURN_ON, - SERVICE_UNLOCK, SERVICE_VOLUME_SET, TEMP_FAHRENHEIT, TEMP_CELSIUS, - STATE_LOCKED, STATE_UNLOCKED, STATE_ON) + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + ATTR_TEMPERATURE, + ATTR_UNIT_OF_MEASUREMENT, + CONF_NAME, + SERVICE_LOCK, + SERVICE_MEDIA_NEXT_TRACK, + SERVICE_MEDIA_PAUSE, + SERVICE_MEDIA_PLAY, + SERVICE_MEDIA_PREVIOUS_TRACK, + SERVICE_MEDIA_STOP, + SERVICE_SET_COVER_POSITION, + SERVICE_TURN_OFF, + SERVICE_TURN_ON, + SERVICE_UNLOCK, + SERVICE_VOLUME_SET, + TEMP_FAHRENHEIT, + TEMP_CELSIUS, + STATE_LOCKED, + STATE_UNLOCKED, + STATE_ON, +) from .const import CONF_FILTER, CONF_ENTITY_CONFIG _LOGGER = logging.getLogger(__name__) -API_DIRECTIVE = 'directive' -API_ENDPOINT = 'endpoint' -API_EVENT = 'event' -API_CONTEXT = 'context' -API_HEADER = 'header' -API_PAYLOAD = 'payload' +API_DIRECTIVE = "directive" +API_ENDPOINT = "endpoint" +API_EVENT = "event" +API_CONTEXT = "context" +API_HEADER = "header" +API_PAYLOAD = "payload" -API_TEMP_UNITS = { - TEMP_FAHRENHEIT: 'FAHRENHEIT', - TEMP_CELSIUS: 'CELSIUS', -} +API_TEMP_UNITS = {TEMP_FAHRENHEIT: "FAHRENHEIT", TEMP_CELSIUS: "CELSIUS"} API_THERMOSTAT_MODES = { - climate.STATE_HEAT: 'HEAT', - climate.STATE_COOL: 'COOL', - climate.STATE_AUTO: 'AUTO', - climate.STATE_ECO: 'ECO', - climate.STATE_IDLE: 'OFF', - climate.STATE_FAN_ONLY: 'OFF', - climate.STATE_DRY: 'OFF', + climate.STATE_HEAT: "HEAT", + climate.STATE_COOL: "COOL", + climate.STATE_AUTO: "AUTO", + climate.STATE_ECO: "ECO", + climate.STATE_IDLE: "OFF", + climate.STATE_FAN_ONLY: "OFF", + climate.STATE_DRY: "OFF", } -SMART_HOME_HTTP_ENDPOINT = '/api/alexa/smart_home' +SMART_HOME_HTTP_ENDPOINT = "/api/alexa/smart_home" -CONF_DESCRIPTION = 'description' -CONF_DISPLAY_CATEGORIES = 'display_categories' +CONF_DESCRIPTION = "description" +CONF_DISPLAY_CATEGORIES = "display_categories" HANDLERS = Registry() ENTITY_ADAPTERS = Registry() -EVENT_ALEXA_SMART_HOME = 'alexa_smart_home' +EVENT_ALEXA_SMART_HOME = "alexa_smart_home" class _DisplayCategory: @@ -112,12 +138,14 @@ class _DisplayCategory: TV = "TV" -def _capability(interface, - version=3, - supports_deactivation=None, - retrievable=None, - properties_supported=None, - cap_type='AlexaInterface'): +def _capability( + interface, + version=3, + supports_deactivation=None, + retrievable=None, + properties_supported=None, + cap_type="AlexaInterface", +): """Return a Smart Home API capability object. https://developer.amazon.com/docs/device-apis/alexa-discovery.html#capability-object @@ -129,20 +157,16 @@ def _capability(interface, `supports_deactivation` applies only to scenes. """ - result = { - 'type': cap_type, - 'interface': interface, - 'version': version, - } + result = {"type": cap_type, "interface": interface, "version": version} if supports_deactivation is not None: - result['supportsDeactivation'] = supports_deactivation + result["supportsDeactivation"] = supports_deactivation if retrievable is not None: - result['retrievable'] = retrievable + result["retrievable"] = retrievable if properties_supported is not None: - result['properties'] = {'supported': properties_supported} + result["properties"] = {"supported": properties_supported} return result @@ -177,7 +201,7 @@ class _AlexaEntity: def entity_id(self): """Return the Alexa API entity id.""" - return self.entity.entity_id.replace('.', '#') + return self.entity.entity_id.replace(".", "#") def display_categories(self): """Return a list of display categories.""" @@ -253,74 +277,70 @@ class _AlexaInterface: def serialize_discovery(self): """Serialize according to the Discovery API.""" result = { - 'type': 'AlexaInterface', - 'interface': self.name(), - 'version': '3', - 'properties': { - 'supported': self.properties_supported(), - 'proactivelyReported': self.properties_proactively_reported(), - 'retrievable': self.properties_retrievable(), + "type": "AlexaInterface", + "interface": self.name(), + "version": "3", + "properties": { + "supported": self.properties_supported(), + "proactivelyReported": self.properties_proactively_reported(), + "retrievable": self.properties_retrievable(), }, } # pylint: disable=assignment-from-none supports_deactivation = self.supports_deactivation() if supports_deactivation is not None: - result['supportsDeactivation'] = supports_deactivation + result["supportsDeactivation"] = supports_deactivation return result def serialize_properties(self): """Return properties serialized for an API response.""" for prop in self.properties_supported(): - prop_name = prop['name'] + prop_name = prop["name"] # pylint: disable=assignment-from-no-return prop_value = self.get_property(prop_name) if prop_value is not None: - yield { - 'name': prop_name, - 'namespace': self.name(), - 'value': prop_value, - } + yield {"name": prop_name, "namespace": self.name(), "value": prop_value} class _AlexaPowerController(_AlexaInterface): def name(self): - return 'Alexa.PowerController' + return "Alexa.PowerController" def properties_supported(self): - return [{'name': 'powerState'}] + return [{"name": "powerState"}] def properties_retrievable(self): return True def get_property(self, name): - if name != 'powerState': + if name != "powerState": raise _UnsupportedProperty(name) if self.entity.state == STATE_ON: - return 'ON' - return 'OFF' + return "ON" + return "OFF" class _AlexaLockController(_AlexaInterface): def name(self): - return 'Alexa.LockController' + return "Alexa.LockController" def properties_supported(self): - return [{'name': 'lockState'}] + return [{"name": "lockState"}] def properties_retrievable(self): return True def get_property(self, name): - if name != 'lockState': + if name != "lockState": raise _UnsupportedProperty(name) if self.entity.state == STATE_LOCKED: - return 'LOCKED' + return "LOCKED" if self.entity.state == STATE_UNLOCKED: - return 'UNLOCKED' - return 'JAMMED' + return "UNLOCKED" + return "JAMMED" class _AlexaSceneController(_AlexaInterface): @@ -329,60 +349,60 @@ class _AlexaSceneController(_AlexaInterface): self.supports_deactivation = lambda: supports_deactivation def name(self): - return 'Alexa.SceneController' + return "Alexa.SceneController" class _AlexaBrightnessController(_AlexaInterface): def name(self): - return 'Alexa.BrightnessController' + return "Alexa.BrightnessController" def properties_supported(self): - return [{'name': 'brightness'}] + return [{"name": "brightness"}] def properties_retrievable(self): return True def get_property(self, name): - if name != 'brightness': + if name != "brightness": raise _UnsupportedProperty(name) - if 'brightness' in self.entity.attributes: - return round(self.entity.attributes['brightness'] / 255.0 * 100) + if "brightness" in self.entity.attributes: + return round(self.entity.attributes["brightness"] / 255.0 * 100) return 0 class _AlexaColorController(_AlexaInterface): def name(self): - return 'Alexa.ColorController' + return "Alexa.ColorController" class _AlexaColorTemperatureController(_AlexaInterface): def name(self): - return 'Alexa.ColorTemperatureController' + return "Alexa.ColorTemperatureController" class _AlexaPercentageController(_AlexaInterface): def name(self): - return 'Alexa.PercentageController' + return "Alexa.PercentageController" class _AlexaSpeaker(_AlexaInterface): def name(self): - return 'Alexa.Speaker' + return "Alexa.Speaker" class _AlexaStepSpeaker(_AlexaInterface): def name(self): - return 'Alexa.StepSpeaker' + return "Alexa.StepSpeaker" class _AlexaPlaybackController(_AlexaInterface): def name(self): - return 'Alexa.PlaybackController' + return "Alexa.PlaybackController" class _AlexaInputController(_AlexaInterface): def name(self): - return 'Alexa.InputController' + return "Alexa.InputController" class _AlexaTemperatureSensor(_AlexaInterface): @@ -391,28 +411,24 @@ class _AlexaTemperatureSensor(_AlexaInterface): self.hass = hass def name(self): - return 'Alexa.TemperatureSensor' + return "Alexa.TemperatureSensor" def properties_supported(self): - return [{'name': 'temperature'}] + return [{"name": "temperature"}] def properties_retrievable(self): return True def get_property(self, name): - if name != 'temperature': + if name != "temperature": raise _UnsupportedProperty(name) unit = self.entity.attributes.get(ATTR_UNIT_OF_MEASUREMENT) temp = self.entity.state if self.entity.domain == climate.DOMAIN: unit = self.hass.config.units.temperature_unit - temp = self.entity.attributes.get( - climate.ATTR_CURRENT_TEMPERATURE) - return { - 'value': float(temp), - 'scale': API_TEMP_UNITS[unit], - } + temp = self.entity.attributes.get(climate.ATTR_CURRENT_TEMPERATURE) + return {"value": float(temp), "scale": API_TEMP_UNITS[unit]} class _AlexaThermostatController(_AlexaInterface): @@ -421,41 +437,45 @@ class _AlexaThermostatController(_AlexaInterface): self.hass = hass def name(self): - return 'Alexa.ThermostatController' + return "Alexa.ThermostatController" def properties_supported(self): properties = [] supported = self.entity.attributes.get(ATTR_SUPPORTED_FEATURES, 0) if supported & climate.SUPPORT_TARGET_TEMPERATURE: - properties.append({'name': 'targetSetpoint'}) + properties.append({"name": "targetSetpoint"}) if supported & climate.SUPPORT_TARGET_TEMPERATURE_LOW: - properties.append({'name': 'lowerSetpoint'}) + properties.append({"name": "lowerSetpoint"}) if supported & climate.SUPPORT_TARGET_TEMPERATURE_HIGH: - properties.append({'name': 'upperSetpoint'}) + properties.append({"name": "upperSetpoint"}) if supported & climate.SUPPORT_OPERATION_MODE: - properties.append({'name': 'thermostatMode'}) + properties.append({"name": "thermostatMode"}) return properties def properties_retrievable(self): return True def get_property(self, name): - if name == 'thermostatMode': + if name == "thermostatMode": ha_mode = self.entity.attributes.get(climate.ATTR_OPERATION_MODE) mode = API_THERMOSTAT_MODES.get(ha_mode) if mode is None: - _LOGGER.error("%s (%s) has unsupported %s value '%s'", - self.entity.entity_id, type(self.entity), - climate.ATTR_OPERATION_MODE, ha_mode) + _LOGGER.error( + "%s (%s) has unsupported %s value '%s'", + self.entity.entity_id, + type(self.entity), + climate.ATTR_OPERATION_MODE, + ha_mode, + ) raise _UnsupportedProperty(name) return mode unit = self.hass.config.units.temperature_unit - if name == 'targetSetpoint': + if name == "targetSetpoint": temp = self.entity.attributes.get(climate.ATTR_TEMPERATURE) - elif name == 'lowerSetpoint': + elif name == "lowerSetpoint": temp = self.entity.attributes.get(climate.ATTR_TARGET_TEMP_LOW) - elif name == 'upperSetpoint': + elif name == "upperSetpoint": temp = self.entity.attributes.get(climate.ATTR_TARGET_TEMP_HIGH) else: raise _UnsupportedProperty(name) @@ -463,10 +483,7 @@ class _AlexaThermostatController(_AlexaInterface): if temp is None: return None - return { - 'value': float(temp), - 'scale': API_TEMP_UNITS[unit], - } + return {"value": float(temp), "scale": API_TEMP_UNITS[unit]} @ENTITY_ADAPTERS.register(alert.DOMAIN) @@ -567,16 +584,19 @@ class _MediaPlayerCapabilities(_AlexaEntity): if supported & media_player.SUPPORT_VOLUME_SET: yield _AlexaSpeaker(self.entity) - step_volume_features = (media_player.SUPPORT_VOLUME_MUTE | - media_player.SUPPORT_VOLUME_STEP) + step_volume_features = ( + media_player.SUPPORT_VOLUME_MUTE | media_player.SUPPORT_VOLUME_STEP + ) if supported & step_volume_features: yield _AlexaStepSpeaker(self.entity) - playback_features = (media_player.SUPPORT_PLAY | - media_player.SUPPORT_PAUSE | - media_player.SUPPORT_STOP | - media_player.SUPPORT_NEXT_TRACK | - media_player.SUPPORT_PREVIOUS_TRACK) + playback_features = ( + media_player.SUPPORT_PLAY + | media_player.SUPPORT_PAUSE + | media_player.SUPPORT_STOP + | media_player.SUPPORT_NEXT_TRACK + | media_player.SUPPORT_PREVIOUS_TRACK + ) if supported & playback_features: yield _AlexaPlaybackController(self.entity) @@ -588,15 +608,14 @@ class _MediaPlayerCapabilities(_AlexaEntity): class _SceneCapabilities(_AlexaEntity): def description(self): # Required description as per Amazon Scene docs - scene_fmt = '{} (Scene connected via Home Assistant)' + scene_fmt = "{} (Scene connected via Home Assistant)" return scene_fmt.format(_AlexaEntity.description(self)) def default_display_categories(self): return [_DisplayCategory.SCENE_TRIGGER] def interfaces(self): - return [_AlexaSceneController(self.entity, - supports_deactivation=False)] + return [_AlexaSceneController(self.entity, supports_deactivation=False)] @ENTITY_ADAPTERS.register(script.DOMAIN) @@ -605,9 +624,8 @@ class _ScriptCapabilities(_AlexaEntity): return [_DisplayCategory.ACTIVITY_TRIGGER] def interfaces(self): - can_cancel = bool(self.entity.attributes.get('can_cancel')) - return [_AlexaSceneController(self.entity, - supports_deactivation=can_cancel)] + can_cancel = bool(self.entity.attributes.get("can_cancel")) + return [_AlexaSceneController(self.entity, supports_deactivation=can_cancel)] @ENTITY_ADAPTERS.register(sensor.DOMAIN) @@ -619,10 +637,7 @@ class _SensorCapabilities(_AlexaEntity): def interfaces(self): attrs = self.entity.attributes - if attrs.get(ATTR_UNIT_OF_MEASUREMENT) in ( - TEMP_FAHRENHEIT, - TEMP_CELSIUS, - ): + if attrs.get(ATTR_UNIT_OF_MEASUREMENT) in (TEMP_FAHRENHEIT, TEMP_CELSIUS): yield _AlexaTemperatureSensor(self.hass, self.entity) @@ -635,28 +650,28 @@ class _Cause: # Indicates that the event was caused by a customer interaction with an # application. For example, a customer switches on a light, or locks a door # using the Alexa app or an app provided by a device vendor. - APP_INTERACTION = 'APP_INTERACTION' + APP_INTERACTION = "APP_INTERACTION" # Indicates that the event was caused by a physical interaction with an # endpoint. For example manually switching on a light or manually locking a # door lock - PHYSICAL_INTERACTION = 'PHYSICAL_INTERACTION' + PHYSICAL_INTERACTION = "PHYSICAL_INTERACTION" # Indicates that the event was caused by the periodic poll of an appliance, # which found a change in value. For example, you might poll a temperature # sensor every hour, and send the updated temperature to Alexa. - PERIODIC_POLL = 'PERIODIC_POLL' + PERIODIC_POLL = "PERIODIC_POLL" # Indicates that the event was caused by the application of a device rule. # For example, a customer configures a rule to switch on a light if a # motion sensor detects motion. In this case, Alexa receives an event from # the motion sensor, and another event from the light to indicate that its # state change was caused by the rule. - RULE_TRIGGER = 'RULE_TRIGGER' + RULE_TRIGGER = "RULE_TRIGGER" # Indicates that the event was caused by a voice interaction with Alexa. # For example a user speaking to their Echo device. - VOICE_INTERACTION = 'VOICE_INTERACTION' + VOICE_INTERACTION = "VOICE_INTERACTION" class Config: @@ -679,8 +694,7 @@ def async_setup(hass, config): by the cloud component which will call async_handle_message directly. """ smart_home_config = Config( - should_expose=config[CONF_FILTER], - entity_config=config.get(CONF_ENTITY_CONFIG), + should_expose=config[CONF_FILTER], entity_config=config.get(CONF_ENTITY_CONFIG) ) hass.http.register_view(SmartHomeView(smart_home_config)) @@ -689,7 +703,7 @@ class SmartHomeView(http.HomeAssistantView): """Expose Smart Home v3 payload interface via HTTP POST.""" url = SMART_HOME_HTTP_ENDPOINT - name = 'api:alexa:smart_home' + name = "api:alexa:smart_home" def __init__(self, smart_home_config): """Initialize.""" @@ -703,65 +717,65 @@ class SmartHomeView(http.HomeAssistantView): Lambda, which will need to forward the requests to here and pass back the response. """ - hass = request.app['hass'] + hass = request.app["hass"] message = yield from request.json() _LOGGER.debug("Received Alexa Smart Home request: %s", message) response = yield from async_handle_message( - hass, self.smart_home_config, message) + hass, self.smart_home_config, message + ) _LOGGER.debug("Sending Alexa Smart Home response: %s", response) - return b'' if response is None else self.json(response) + return b"" if response is None else self.json(response) async def async_handle_message(hass, config, request, context=None): """Handle incoming API messages.""" - assert request[API_DIRECTIVE][API_HEADER]['payloadVersion'] == '3' + assert request[API_DIRECTIVE][API_HEADER]["payloadVersion"] == "3" if context is None: context = ha.Context() # Read head data request = request[API_DIRECTIVE] - namespace = request[API_HEADER]['namespace'] - name = request[API_HEADER]['name'] + namespace = request[API_HEADER]["namespace"] + name = request[API_HEADER]["name"] # Do we support this API request? funct_ref = HANDLERS.get((namespace, name)) if funct_ref: response = await funct_ref(hass, config, request, context) else: - _LOGGER.warning( - "Unsupported API request %s/%s", namespace, name) + _LOGGER.warning("Unsupported API request %s/%s", namespace, name) response = api_error(request) - request_info = { - 'namespace': namespace, - 'name': name, - } + request_info = {"namespace": namespace, "name": name} - if API_ENDPOINT in request and 'endpointId' in request[API_ENDPOINT]: - request_info['entity_id'] = \ - request[API_ENDPOINT]['endpointId'].replace('#', '.') + if API_ENDPOINT in request and "endpointId" in request[API_ENDPOINT]: + request_info["entity_id"] = request[API_ENDPOINT]["endpointId"].replace( + "#", "." + ) response_header = response[API_EVENT][API_HEADER] - hass.bus.async_fire(EVENT_ALEXA_SMART_HOME, { - 'request': request_info, - 'response': { - 'namespace': response_header['namespace'], - 'name': response_header['name'], - } - }, context=context) + hass.bus.async_fire( + EVENT_ALEXA_SMART_HOME, + { + "request": request_info, + "response": { + "namespace": response_header["namespace"], + "name": response_header["name"], + }, + }, + context=context, + ) return response -def api_message(request, - name='Response', - namespace='Alexa', - payload=None, - context=None): +def api_message( + request, name="Response", namespace="Alexa", payload=None, context=None +): """Create a API formatted response message. Async friendly. @@ -771,19 +785,19 @@ def api_message(request, response = { API_EVENT: { API_HEADER: { - 'namespace': namespace, - 'name': name, - 'messageId': str(uuid4()), - 'payloadVersion': '3', + "namespace": namespace, + "name": name, + "messageId": str(uuid4()), + "payloadVersion": "3", }, API_PAYLOAD: payload, } } # If a correlation token exists, add it to header / Need by Async requests - token = request[API_HEADER].get('correlationToken') + token = request[API_HEADER].get("correlationToken") if token: - response[API_EVENT][API_HEADER]['correlationToken'] = token + response[API_EVENT][API_HEADER]["correlationToken"] = token # Extend event with endpoint object / Need by Async requests if API_ENDPOINT in request: @@ -795,29 +809,35 @@ def api_message(request, return response -def api_error(request, - namespace='Alexa', - error_type='INTERNAL_ERROR', - error_message="", - payload=None): +def api_error( + request, + namespace="Alexa", + error_type="INTERNAL_ERROR", + error_message="", + payload=None, +): """Create a API formatted error response. Async friendly. """ payload = payload or {} - payload['type'] = error_type - payload['message'] = error_message + payload["type"] = error_type + payload["message"] = error_message - _LOGGER.info("Request %s/%s error %s: %s", - request[API_HEADER]['namespace'], - request[API_HEADER]['name'], - error_type, error_message) + _LOGGER.info( + "Request %s/%s error %s: %s", + request[API_HEADER]["namespace"], + request[API_HEADER]["name"], + error_type, + error_message, + ) return api_message( - request, name='ErrorResponse', namespace=namespace, payload=payload) + request, name="ErrorResponse", namespace=namespace, payload=payload + ) -@HANDLERS.register(('Alexa.Discovery', 'Discover')) +@HANDLERS.register(("Alexa.Discovery", "Discover")) async def async_api_discovery(hass, config, request, context): """Create a API formatted discovery response. @@ -827,8 +847,9 @@ async def async_api_discovery(hass, config, request, context): for entity in hass.states.async_all(): if not config.should_expose(entity.entity_id): - _LOGGER.debug("Not exposing %s because filtered by config", - entity.entity_id) + _LOGGER.debug( + "Not exposing %s because filtered by config", entity.entity_id + ) continue if entity.domain not in ENTITY_ADAPTERS: @@ -836,47 +857,54 @@ async def async_api_discovery(hass, config, request, context): alexa_entity = ENTITY_ADAPTERS[entity.domain](hass, config, entity) endpoint = { - 'displayCategories': alexa_entity.display_categories(), - 'additionalApplianceDetails': {}, - 'endpointId': alexa_entity.entity_id(), - 'friendlyName': alexa_entity.friendly_name(), - 'description': alexa_entity.description(), - 'manufacturerName': 'Home Assistant', + "displayCategories": alexa_entity.display_categories(), + "additionalApplianceDetails": {}, + "endpointId": alexa_entity.entity_id(), + "friendlyName": alexa_entity.friendly_name(), + "description": alexa_entity.description(), + "manufacturerName": "Home Assistant", } - endpoint['capabilities'] = [ - i.serialize_discovery() for i in alexa_entity.interfaces()] + endpoint["capabilities"] = [ + i.serialize_discovery() for i in alexa_entity.interfaces() + ] - if not endpoint['capabilities']: - _LOGGER.debug("Not exposing %s because it has no capabilities", - entity.entity_id) + if not endpoint["capabilities"]: + _LOGGER.debug( + "Not exposing %s because it has no capabilities", entity.entity_id + ) continue discovery_endpoints.append(endpoint) return api_message( - request, name='Discover.Response', namespace='Alexa.Discovery', - payload={'endpoints': discovery_endpoints}) + request, + name="Discover.Response", + namespace="Alexa.Discovery", + payload={"endpoints": discovery_endpoints}, + ) def extract_entity(funct): """Decorate for extract entity object from request.""" + async def async_api_entity_wrapper(hass, config, request, context): """Process a turn on request.""" - entity_id = request[API_ENDPOINT]['endpointId'].replace('#', '.') + entity_id = request[API_ENDPOINT]["endpointId"].replace("#", ".") # extract state object entity = hass.states.get(entity_id) if not entity: - _LOGGER.error("Can't process %s for %s", - request[API_HEADER]['name'], entity_id) - return api_error(request, error_type='NO_SUCH_ENDPOINT') + _LOGGER.error( + "Can't process %s for %s", request[API_HEADER]["name"], entity_id + ) + return api_error(request, error_type="NO_SUCH_ENDPOINT") return await funct(hass, config, request, context, entity) return async_api_entity_wrapper -@HANDLERS.register(('Alexa.PowerController', 'TurnOn')) +@HANDLERS.register(("Alexa.PowerController", "TurnOn")) @extract_entity async def async_api_turn_on(hass, config, request, context, entity): """Process a turn on request.""" @@ -888,14 +916,18 @@ async def async_api_turn_on(hass, config, request, context, entity): if entity.domain == cover.DOMAIN: service = cover.SERVICE_OPEN_COVER - await hass.services.async_call(domain, service, { - ATTR_ENTITY_ID: entity.entity_id - }, blocking=False, context=context) + await hass.services.async_call( + domain, + service, + {ATTR_ENTITY_ID: entity.entity_id}, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa.PowerController', 'TurnOff')) +@HANDLERS.register(("Alexa.PowerController", "TurnOff")) @extract_entity async def async_api_turn_off(hass, config, request, context, entity): """Process a turn off request.""" @@ -907,170 +939,196 @@ async def async_api_turn_off(hass, config, request, context, entity): if entity.domain == cover.DOMAIN: service = cover.SERVICE_CLOSE_COVER - await hass.services.async_call(domain, service, { - ATTR_ENTITY_ID: entity.entity_id - }, blocking=False, context=context) + await hass.services.async_call( + domain, + service, + {ATTR_ENTITY_ID: entity.entity_id}, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa.BrightnessController', 'SetBrightness')) +@HANDLERS.register(("Alexa.BrightnessController", "SetBrightness")) @extract_entity async def async_api_set_brightness(hass, config, request, context, entity): """Process a set brightness request.""" - brightness = int(request[API_PAYLOAD]['brightness']) + brightness = int(request[API_PAYLOAD]["brightness"]) - await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id, - light.ATTR_BRIGHTNESS_PCT: brightness, - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_BRIGHTNESS_PCT: brightness}, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa.BrightnessController', 'AdjustBrightness')) +@HANDLERS.register(("Alexa.BrightnessController", "AdjustBrightness")) @extract_entity async def async_api_adjust_brightness(hass, config, request, context, entity): """Process an adjust brightness request.""" - brightness_delta = int(request[API_PAYLOAD]['brightnessDelta']) + brightness_delta = int(request[API_PAYLOAD]["brightnessDelta"]) # read current state try: current = math.floor( - int(entity.attributes.get(light.ATTR_BRIGHTNESS)) / 255 * 100) + int(entity.attributes.get(light.ATTR_BRIGHTNESS)) / 255 * 100 + ) except ZeroDivisionError: current = 0 # set brightness brightness = max(0, brightness_delta + current) - await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id, - light.ATTR_BRIGHTNESS_PCT: brightness, - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_BRIGHTNESS_PCT: brightness}, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa.ColorController', 'SetColor')) +@HANDLERS.register(("Alexa.ColorController", "SetColor")) @extract_entity async def async_api_set_color(hass, config, request, context, entity): """Process a set color request.""" rgb = color_util.color_hsb_to_RGB( - float(request[API_PAYLOAD]['color']['hue']), - float(request[API_PAYLOAD]['color']['saturation']), - float(request[API_PAYLOAD]['color']['brightness']) + float(request[API_PAYLOAD]["color"]["hue"]), + float(request[API_PAYLOAD]["color"]["saturation"]), + float(request[API_PAYLOAD]["color"]["brightness"]), ) - await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id, - light.ATTR_RGB_COLOR: rgb, - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_RGB_COLOR: rgb}, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa.ColorTemperatureController', 'SetColorTemperature')) +@HANDLERS.register(("Alexa.ColorTemperatureController", "SetColorTemperature")) @extract_entity -async def async_api_set_color_temperature(hass, config, request, context, - entity): +async def async_api_set_color_temperature(hass, config, request, context, entity): """Process a set color temperature request.""" - kelvin = int(request[API_PAYLOAD]['colorTemperatureInKelvin']) + kelvin = int(request[API_PAYLOAD]["colorTemperatureInKelvin"]) - await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id, - light.ATTR_KELVIN: kelvin, - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_KELVIN: kelvin}, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register( - ('Alexa.ColorTemperatureController', 'DecreaseColorTemperature')) +@HANDLERS.register(("Alexa.ColorTemperatureController", "DecreaseColorTemperature")) @extract_entity -async def async_api_decrease_color_temp(hass, config, request, context, - entity): +async def async_api_decrease_color_temp(hass, config, request, context, entity): """Process a decrease color temperature request.""" current = int(entity.attributes.get(light.ATTR_COLOR_TEMP)) max_mireds = int(entity.attributes.get(light.ATTR_MAX_MIREDS)) value = min(max_mireds, current + 50) - await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id, - light.ATTR_COLOR_TEMP: value, - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_COLOR_TEMP: value}, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register( - ('Alexa.ColorTemperatureController', 'IncreaseColorTemperature')) +@HANDLERS.register(("Alexa.ColorTemperatureController", "IncreaseColorTemperature")) @extract_entity -async def async_api_increase_color_temp(hass, config, request, context, - entity): +async def async_api_increase_color_temp(hass, config, request, context, entity): """Process an increase color temperature request.""" current = int(entity.attributes.get(light.ATTR_COLOR_TEMP)) min_mireds = int(entity.attributes.get(light.ATTR_MIN_MIREDS)) value = max(min_mireds, current - 50) - await hass.services.async_call(entity.domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id, - light.ATTR_COLOR_TEMP: value, - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id, light.ATTR_COLOR_TEMP: value}, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa.SceneController', 'Activate')) +@HANDLERS.register(("Alexa.SceneController", "Activate")) @extract_entity async def async_api_activate(hass, config, request, context, entity): """Process an activate request.""" domain = entity.domain - await hass.services.async_call(domain, SERVICE_TURN_ON, { - ATTR_ENTITY_ID: entity.entity_id - }, blocking=False, context=context) + await hass.services.async_call( + domain, + SERVICE_TURN_ON, + {ATTR_ENTITY_ID: entity.entity_id}, + blocking=False, + context=context, + ) payload = { - 'cause': {'type': _Cause.VOICE_INTERACTION}, - 'timestamp': '%sZ' % (datetime.utcnow().isoformat(),) + "cause": {"type": _Cause.VOICE_INTERACTION}, + "timestamp": "%sZ" % (datetime.utcnow().isoformat(),), } return api_message( request, - name='ActivationStarted', - namespace='Alexa.SceneController', + name="ActivationStarted", + namespace="Alexa.SceneController", payload=payload, ) -@HANDLERS.register(('Alexa.SceneController', 'Deactivate')) +@HANDLERS.register(("Alexa.SceneController", "Deactivate")) @extract_entity async def async_api_deactivate(hass, config, request, context, entity): """Process a deactivate request.""" domain = entity.domain - await hass.services.async_call(domain, SERVICE_TURN_OFF, { - ATTR_ENTITY_ID: entity.entity_id - }, blocking=False, context=context) + await hass.services.async_call( + domain, + SERVICE_TURN_OFF, + {ATTR_ENTITY_ID: entity.entity_id}, + blocking=False, + context=context, + ) payload = { - 'cause': {'type': _Cause.VOICE_INTERACTION}, - 'timestamp': '%sZ' % (datetime.utcnow().isoformat(),) + "cause": {"type": _Cause.VOICE_INTERACTION}, + "timestamp": "%sZ" % (datetime.utcnow().isoformat(),), } return api_message( request, - name='DeactivationStarted', - namespace='Alexa.SceneController', + name="DeactivationStarted", + namespace="Alexa.SceneController", payload=payload, ) -@HANDLERS.register(('Alexa.PercentageController', 'SetPercentage')) +@HANDLERS.register(("Alexa.PercentageController", "SetPercentage")) @extract_entity async def async_api_set_percentage(hass, config, request, context, entity): """Process a set percentage request.""" - percentage = int(request[API_PAYLOAD]['percentage']) + percentage = int(request[API_PAYLOAD]["percentage"]) service = None data = {ATTR_ENTITY_ID: entity.entity_id} @@ -1091,16 +1149,17 @@ async def async_api_set_percentage(hass, config, request, context, entity): data[cover.ATTR_POSITION] = percentage await hass.services.async_call( - entity.domain, service, data, blocking=False, context=context) + entity.domain, service, data, blocking=False, context=context + ) return api_message(request) -@HANDLERS.register(('Alexa.PercentageController', 'AdjustPercentage')) +@HANDLERS.register(("Alexa.PercentageController", "AdjustPercentage")) @extract_entity async def async_api_adjust_percentage(hass, config, request, context, entity): """Process an adjust percentage request.""" - percentage_delta = int(request[API_PAYLOAD]['percentageDelta']) + percentage_delta = int(request[API_PAYLOAD]["percentageDelta"]) service = None data = {ATTR_ENTITY_ID: entity.entity_id} @@ -1138,48 +1197,55 @@ async def async_api_adjust_percentage(hass, config, request, context, entity): data[cover.ATTR_POSITION] = max(0, percentage_delta + current) await hass.services.async_call( - entity.domain, service, data, blocking=False, context=context) + entity.domain, service, data, blocking=False, context=context + ) return api_message(request) -@HANDLERS.register(('Alexa.LockController', 'Lock')) +@HANDLERS.register(("Alexa.LockController", "Lock")) @extract_entity async def async_api_lock(hass, config, request, context, entity): """Process a lock request.""" - await hass.services.async_call(entity.domain, SERVICE_LOCK, { - ATTR_ENTITY_ID: entity.entity_id - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_LOCK, + {ATTR_ENTITY_ID: entity.entity_id}, + blocking=False, + context=context, + ) # Alexa expects a lockState in the response, we don't know the actual # lockState at this point but assume it is locked. It is reported # correctly later when ReportState is called. The alt. to this approach # is to implement DeferredResponse - properties = [{ - 'name': 'lockState', - 'namespace': 'Alexa.LockController', - 'value': 'LOCKED' - }] - return api_message(request, context={'properties': properties}) + properties = [ + {"name": "lockState", "namespace": "Alexa.LockController", "value": "LOCKED"} + ] + return api_message(request, context={"properties": properties}) # Not supported by Alexa yet -@HANDLERS.register(('Alexa.LockController', 'Unlock')) +@HANDLERS.register(("Alexa.LockController", "Unlock")) @extract_entity async def async_api_unlock(hass, config, request, context, entity): """Process an unlock request.""" - await hass.services.async_call(entity.domain, SERVICE_UNLOCK, { - ATTR_ENTITY_ID: entity.entity_id - }, blocking=False, context=context) + await hass.services.async_call( + entity.domain, + SERVICE_UNLOCK, + {ATTR_ENTITY_ID: entity.entity_id}, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa.Speaker', 'SetVolume')) +@HANDLERS.register(("Alexa.Speaker", "SetVolume")) @extract_entity async def async_api_set_volume(hass, config, request, context, entity): """Process a set volume request.""" - volume = round(float(request[API_PAYLOAD]['volume'] / 100), 2) + volume = round(float(request[API_PAYLOAD]["volume"] / 100), 2) data = { ATTR_ENTITY_ID: entity.entity_id, @@ -1187,32 +1253,32 @@ async def async_api_set_volume(hass, config, request, context, entity): } await hass.services.async_call( - entity.domain, SERVICE_VOLUME_SET, - data, blocking=False, context=context) + entity.domain, SERVICE_VOLUME_SET, data, blocking=False, context=context + ) return api_message(request) -@HANDLERS.register(('Alexa.InputController', 'SelectInput')) +@HANDLERS.register(("Alexa.InputController", "SelectInput")) @extract_entity async def async_api_select_input(hass, config, request, context, entity): """Process a set input request.""" - media_input = request[API_PAYLOAD]['input'] + media_input = request[API_PAYLOAD]["input"] # attempt to map the ALL UPPERCASE payload name to a source source_list = entity.attributes[media_player.ATTR_INPUT_SOURCE_LIST] or [] for source in source_list: # response will always be space separated, so format the source in the # most likely way to find a match - formatted_source = source.lower().replace('-', ' ').replace('_', ' ') + formatted_source = source.lower().replace("-", " ").replace("_", " ") if formatted_source in media_input.lower(): media_input = source break else: - msg = 'failed to map input {} to a media source on {}'.format( - media_input, entity.entity_id) - return api_error( - request, error_type='INVALID_VALUE', error_message=msg) + msg = "failed to map input {} to a media source on {}".format( + media_input, entity.entity_id + ) + return api_error(request, error_type="INVALID_VALUE", error_message=msg) data = { ATTR_ENTITY_ID: entity.entity_id, @@ -1220,17 +1286,21 @@ async def async_api_select_input(hass, config, request, context, entity): } await hass.services.async_call( - entity.domain, media_player.SERVICE_SELECT_SOURCE, - data, blocking=False, context=context) + entity.domain, + media_player.SERVICE_SELECT_SOURCE, + data, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa.Speaker', 'AdjustVolume')) +@HANDLERS.register(("Alexa.Speaker", "AdjustVolume")) @extract_entity async def async_api_adjust_volume(hass, config, request, context, entity): """Process an adjust volume request.""" - volume_delta = int(request[API_PAYLOAD]['volume']) + volume_delta = int(request[API_PAYLOAD]["volume"]) current_level = entity.attributes.get(media_player.ATTR_MEDIA_VOLUME_LEVEL) @@ -1248,13 +1318,17 @@ async def async_api_adjust_volume(hass, config, request, context, entity): } await hass.services.async_call( - entity.domain, media_player.SERVICE_VOLUME_SET, - data, blocking=False, context=context) + entity.domain, + media_player.SERVICE_VOLUME_SET, + data, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa.StepSpeaker', 'AdjustVolume')) +@HANDLERS.register(("Alexa.StepSpeaker", "AdjustVolume")) @extract_entity async def async_api_adjust_volume_step(hass, config, request, context, entity): """Process an adjust volume step request.""" @@ -1262,30 +1336,36 @@ async def async_api_adjust_volume_step(hass, config, request, context, entity): # each component handles it differently e.g. via config. # For now we use the volumeSteps returned to figure out if we # should step up/down - volume_step = request[API_PAYLOAD]['volumeSteps'] + volume_step = request[API_PAYLOAD]["volumeSteps"] - data = { - ATTR_ENTITY_ID: entity.entity_id, - } + data = {ATTR_ENTITY_ID: entity.entity_id} if volume_step > 0: await hass.services.async_call( - entity.domain, media_player.SERVICE_VOLUME_UP, - data, blocking=False, context=context) + entity.domain, + media_player.SERVICE_VOLUME_UP, + data, + blocking=False, + context=context, + ) elif volume_step < 0: await hass.services.async_call( - entity.domain, media_player.SERVICE_VOLUME_DOWN, - data, blocking=False, context=context) + entity.domain, + media_player.SERVICE_VOLUME_DOWN, + data, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa.StepSpeaker', 'SetMute')) -@HANDLERS.register(('Alexa.Speaker', 'SetMute')) +@HANDLERS.register(("Alexa.StepSpeaker", "SetMute")) +@HANDLERS.register(("Alexa.Speaker", "SetMute")) @extract_entity async def async_api_set_mute(hass, config, request, context, entity): """Process a set mute request.""" - mute = bool(request[API_PAYLOAD]['mute']) + mute = bool(request[API_PAYLOAD]["mute"]) data = { ATTR_ENTITY_ID: entity.entity_id, @@ -1293,83 +1373,81 @@ async def async_api_set_mute(hass, config, request, context, entity): } await hass.services.async_call( - entity.domain, media_player.SERVICE_VOLUME_MUTE, - data, blocking=False, context=context) + entity.domain, + media_player.SERVICE_VOLUME_MUTE, + data, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa.PlaybackController', 'Play')) +@HANDLERS.register(("Alexa.PlaybackController", "Play")) @extract_entity async def async_api_play(hass, config, request, context, entity): """Process a play request.""" - data = { - ATTR_ENTITY_ID: entity.entity_id - } + data = {ATTR_ENTITY_ID: entity.entity_id} await hass.services.async_call( - entity.domain, SERVICE_MEDIA_PLAY, - data, blocking=False, context=context) + entity.domain, SERVICE_MEDIA_PLAY, data, blocking=False, context=context + ) return api_message(request) -@HANDLERS.register(('Alexa.PlaybackController', 'Pause')) +@HANDLERS.register(("Alexa.PlaybackController", "Pause")) @extract_entity async def async_api_pause(hass, config, request, context, entity): """Process a pause request.""" - data = { - ATTR_ENTITY_ID: entity.entity_id - } + data = {ATTR_ENTITY_ID: entity.entity_id} await hass.services.async_call( - entity.domain, SERVICE_MEDIA_PAUSE, - data, blocking=False, context=context) + entity.domain, SERVICE_MEDIA_PAUSE, data, blocking=False, context=context + ) return api_message(request) -@HANDLERS.register(('Alexa.PlaybackController', 'Stop')) +@HANDLERS.register(("Alexa.PlaybackController", "Stop")) @extract_entity async def async_api_stop(hass, config, request, context, entity): """Process a stop request.""" - data = { - ATTR_ENTITY_ID: entity.entity_id - } + data = {ATTR_ENTITY_ID: entity.entity_id} await hass.services.async_call( - entity.domain, SERVICE_MEDIA_STOP, - data, blocking=False, context=context) + entity.domain, SERVICE_MEDIA_STOP, data, blocking=False, context=context + ) return api_message(request) -@HANDLERS.register(('Alexa.PlaybackController', 'Next')) +@HANDLERS.register(("Alexa.PlaybackController", "Next")) @extract_entity async def async_api_next(hass, config, request, context, entity): """Process a next request.""" - data = { - ATTR_ENTITY_ID: entity.entity_id - } + data = {ATTR_ENTITY_ID: entity.entity_id} await hass.services.async_call( - entity.domain, SERVICE_MEDIA_NEXT_TRACK, - data, blocking=False, context=context) + entity.domain, SERVICE_MEDIA_NEXT_TRACK, data, blocking=False, context=context + ) return api_message(request) -@HANDLERS.register(('Alexa.PlaybackController', 'Previous')) +@HANDLERS.register(("Alexa.PlaybackController", "Previous")) @extract_entity async def async_api_previous(hass, config, request, context, entity): """Process a previous request.""" - data = { - ATTR_ENTITY_ID: entity.entity_id - } + data = {ATTR_ENTITY_ID: entity.entity_id} await hass.services.async_call( - entity.domain, SERVICE_MEDIA_PREVIOUS_TRACK, - data, blocking=False, context=context) + entity.domain, + SERVICE_MEDIA_PREVIOUS_TRACK, + data, + blocking=False, + context=context, + ) return api_message(request) @@ -1381,22 +1459,16 @@ def api_error_temp_range(hass, request, temp, min_temp, max_temp): """ unit = hass.config.units.temperature_unit temp_range = { - 'minimumValue': { - 'value': min_temp, - 'scale': API_TEMP_UNITS[unit], - }, - 'maximumValue': { - 'value': max_temp, - 'scale': API_TEMP_UNITS[unit], - }, + "minimumValue": {"value": min_temp, "scale": API_TEMP_UNITS[unit]}, + "maximumValue": {"value": max_temp, "scale": API_TEMP_UNITS[unit]}, } - msg = 'The requested temperature {} is out of range'.format(temp) + msg = "The requested temperature {} is out of range".format(temp) return api_error( request, - error_type='TEMPERATURE_VALUE_OUT_OF_RANGE', + error_type="TEMPERATURE_VALUE_OUT_OF_RANGE", error_message=msg, - payload={'validRange': temp_range}, + payload={"validRange": temp_range}, ) @@ -1404,11 +1476,11 @@ def temperature_from_object(hass, temp_obj, interval=False): """Get temperature from Temperature object in requested unit.""" to_unit = hass.config.units.temperature_unit from_unit = TEMP_CELSIUS - temp = float(temp_obj['value']) + temp = float(temp_obj["value"]) - if temp_obj['scale'] == 'FAHRENHEIT': + if temp_obj["scale"] == "FAHRENHEIT": from_unit = TEMP_FAHRENHEIT - elif temp_obj['scale'] == 'KELVIN': + elif temp_obj["scale"] == "KELVIN": # convert to Celsius if absolute temperature if not interval: temp -= 273.15 @@ -1416,45 +1488,44 @@ def temperature_from_object(hass, temp_obj, interval=False): return convert_temperature(temp, from_unit, to_unit, interval) -@HANDLERS.register(('Alexa.ThermostatController', 'SetTargetTemperature')) +@HANDLERS.register(("Alexa.ThermostatController", "SetTargetTemperature")) @extract_entity async def async_api_set_target_temp(hass, config, request, context, entity): """Process a set target temperature request.""" min_temp = entity.attributes.get(climate.ATTR_MIN_TEMP) max_temp = entity.attributes.get(climate.ATTR_MAX_TEMP) - data = { - ATTR_ENTITY_ID: entity.entity_id - } + data = {ATTR_ENTITY_ID: entity.entity_id} payload = request[API_PAYLOAD] - if 'targetSetpoint' in payload: - temp = temperature_from_object(hass, payload['targetSetpoint']) + if "targetSetpoint" in payload: + temp = temperature_from_object(hass, payload["targetSetpoint"]) if temp < min_temp or temp > max_temp: - return api_error_temp_range( - hass, request, temp, min_temp, max_temp) + return api_error_temp_range(hass, request, temp, min_temp, max_temp) data[ATTR_TEMPERATURE] = temp - if 'lowerSetpoint' in payload: - temp_low = temperature_from_object(hass, payload['lowerSetpoint']) + if "lowerSetpoint" in payload: + temp_low = temperature_from_object(hass, payload["lowerSetpoint"]) if temp_low < min_temp or temp_low > max_temp: - return api_error_temp_range( - hass, request, temp_low, min_temp, max_temp) + return api_error_temp_range(hass, request, temp_low, min_temp, max_temp) data[climate.ATTR_TARGET_TEMP_LOW] = temp_low - if 'upperSetpoint' in payload: - temp_high = temperature_from_object(hass, payload['upperSetpoint']) + if "upperSetpoint" in payload: + temp_high = temperature_from_object(hass, payload["upperSetpoint"]) if temp_high < min_temp or temp_high > max_temp: - return api_error_temp_range( - hass, request, temp_high, min_temp, max_temp) + return api_error_temp_range(hass, request, temp_high, min_temp, max_temp) data[climate.ATTR_TARGET_TEMP_HIGH] = temp_high await hass.services.async_call( - entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False, - context=context) + entity.domain, + climate.SERVICE_SET_TEMPERATURE, + data, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa.ThermostatController', 'AdjustTargetTemperature')) +@HANDLERS.register(("Alexa.ThermostatController", "AdjustTargetTemperature")) @extract_entity async def async_api_adjust_target_temp(hass, config, request, context, entity): """Process an adjust target temperature request.""" @@ -1462,60 +1533,58 @@ async def async_api_adjust_target_temp(hass, config, request, context, entity): max_temp = entity.attributes.get(climate.ATTR_MAX_TEMP) temp_delta = temperature_from_object( - hass, request[API_PAYLOAD]['targetSetpointDelta'], interval=True) + hass, request[API_PAYLOAD]["targetSetpointDelta"], interval=True + ) target_temp = float(entity.attributes.get(ATTR_TEMPERATURE)) + temp_delta if target_temp < min_temp or target_temp > max_temp: - return api_error_temp_range( - hass, request, target_temp, min_temp, max_temp) + return api_error_temp_range(hass, request, target_temp, min_temp, max_temp) - data = { - ATTR_ENTITY_ID: entity.entity_id, - ATTR_TEMPERATURE: target_temp, - } + data = {ATTR_ENTITY_ID: entity.entity_id, ATTR_TEMPERATURE: target_temp} await hass.services.async_call( - entity.domain, climate.SERVICE_SET_TEMPERATURE, data, blocking=False, - context=context) + entity.domain, + climate.SERVICE_SET_TEMPERATURE, + data, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa.ThermostatController', 'SetThermostatMode')) +@HANDLERS.register(("Alexa.ThermostatController", "SetThermostatMode")) @extract_entity -async def async_api_set_thermostat_mode(hass, config, request, context, - entity): +async def async_api_set_thermostat_mode(hass, config, request, context, entity): """Process a set thermostat mode request.""" - mode = request[API_PAYLOAD]['thermostatMode'] - mode = mode if isinstance(mode, str) else mode['value'] + mode = request[API_PAYLOAD]["thermostatMode"] + mode = mode if isinstance(mode, str) else mode["value"] operation_list = entity.attributes.get(climate.ATTR_OPERATION_LIST) - ha_mode = next( - (k for k, v in API_THERMOSTAT_MODES.items() if v == mode), - None - ) + ha_mode = next((k for k, v in API_THERMOSTAT_MODES.items() if v == mode), None) if ha_mode not in operation_list: - msg = 'The requested thermostat mode {} is not supported'.format(mode) + msg = "The requested thermostat mode {} is not supported".format(mode) return api_error( request, - namespace='Alexa.ThermostatController', - error_type='UNSUPPORTED_THERMOSTAT_MODE', - error_message=msg + namespace="Alexa.ThermostatController", + error_type="UNSUPPORTED_THERMOSTAT_MODE", + error_message=msg, ) - data = { - ATTR_ENTITY_ID: entity.entity_id, - climate.ATTR_OPERATION_MODE: ha_mode, - } + data = {ATTR_ENTITY_ID: entity.entity_id, climate.ATTR_OPERATION_MODE: ha_mode} await hass.services.async_call( - entity.domain, climate.SERVICE_SET_OPERATION_MODE, data, - blocking=False, context=context) + entity.domain, + climate.SERVICE_SET_OPERATION_MODE, + data, + blocking=False, + context=context, + ) return api_message(request) -@HANDLERS.register(('Alexa', 'ReportState')) +@HANDLERS.register(("Alexa", "ReportState")) @extract_entity async def async_api_reportstate(hass, config, request, context, entity): """Process a ReportState request.""" @@ -1524,8 +1593,4 @@ async def async_api_reportstate(hass, config, request, context, entity): for interface in alexa_entity.interfaces(): properties.extend(interface.serialize_properties()) - return api_message( - request, - name='StateReport', - context={'properties': properties} - ) + return api_message(request, name="StateReport", context={"properties": properties}) diff --git a/homeassistant/components/amcrest.py b/homeassistant/components/amcrest.py index bcd0c38c3..688c8e806 100644 --- a/homeassistant/components/amcrest.py +++ b/homeassistant/components/amcrest.py @@ -13,85 +13,100 @@ from requests.exceptions import HTTPError, ConnectTimeout from requests.exceptions import ConnectionError as ConnectError from homeassistant.const import ( - CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, - CONF_SENSORS, CONF_SWITCHES, CONF_SCAN_INTERVAL, HTTP_BASIC_AUTHENTICATION) + CONF_NAME, + CONF_HOST, + CONF_PORT, + CONF_USERNAME, + CONF_PASSWORD, + CONF_SENSORS, + CONF_SWITCHES, + CONF_SCAN_INTERVAL, + HTTP_BASIC_AUTHENTICATION, +) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['amcrest==1.2.3'] -DEPENDENCIES = ['ffmpeg'] +REQUIREMENTS = ["amcrest==1.2.3"] +DEPENDENCIES = ["ffmpeg"] _LOGGER = logging.getLogger(__name__) -CONF_AUTHENTICATION = 'authentication' -CONF_RESOLUTION = 'resolution' -CONF_STREAM_SOURCE = 'stream_source' -CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' +CONF_AUTHENTICATION = "authentication" +CONF_RESOLUTION = "resolution" +CONF_STREAM_SOURCE = "stream_source" +CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments" -DEFAULT_NAME = 'Amcrest Camera' +DEFAULT_NAME = "Amcrest Camera" DEFAULT_PORT = 80 -DEFAULT_RESOLUTION = 'high' -DEFAULT_STREAM_SOURCE = 'snapshot' +DEFAULT_RESOLUTION = "high" +DEFAULT_STREAM_SOURCE = "snapshot" TIMEOUT = 10 -DATA_AMCREST = 'amcrest' -DOMAIN = 'amcrest' +DATA_AMCREST = "amcrest" +DOMAIN = "amcrest" -NOTIFICATION_ID = 'amcrest_notification' -NOTIFICATION_TITLE = 'Amcrest Camera Setup' +NOTIFICATION_ID = "amcrest_notification" +NOTIFICATION_TITLE = "Amcrest Camera Setup" -RESOLUTION_LIST = { - 'high': 0, - 'low': 1, -} +RESOLUTION_LIST = {"high": 0, "low": 1} SCAN_INTERVAL = timedelta(seconds=10) -AUTHENTICATION_LIST = { - 'basic': 'basic' -} +AUTHENTICATION_LIST = {"basic": "basic"} -STREAM_SOURCE_LIST = { - 'mjpeg': 0, - 'snapshot': 1, - 'rtsp': 2, -} +STREAM_SOURCE_LIST = {"mjpeg": 0, "snapshot": 1, "rtsp": 2} # Sensor types are defined like: Name, units, icon SENSORS = { - 'motion_detector': ['Motion Detected', None, 'mdi:run'], - 'sdcard': ['SD Used', '%', 'mdi:sd'], - 'ptz_preset': ['PTZ Preset', None, 'mdi:camera-iris'], + "motion_detector": ["Motion Detected", None, "mdi:run"], + "sdcard": ["SD Used", "%", "mdi:sd"], + "ptz_preset": ["PTZ Preset", None, "mdi:camera-iris"], } # Switch types are defined like: Name, icon SWITCHES = { - 'motion_detection': ['Motion Detection', 'mdi:run-fast'], - 'motion_recording': ['Motion Recording', 'mdi:record-rec'] + "motion_detection": ["Motion Detection", "mdi:run-fast"], + "motion_recording": ["Motion Recording", "mdi:record-rec"], } -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.All(cv.ensure_list, [vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): - vol.All(vol.In(AUTHENTICATION_LIST)), - vol.Optional(CONF_RESOLUTION, default=DEFAULT_RESOLUTION): - vol.All(vol.In(RESOLUTION_LIST)), - vol.Optional(CONF_STREAM_SOURCE, default=DEFAULT_STREAM_SOURCE): - vol.All(vol.In(STREAM_SOURCE_LIST)), - vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string, - vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): - cv.time_period, - vol.Optional(CONF_SENSORS): - vol.All(cv.ensure_list, [vol.In(SENSORS)]), - vol.Optional(CONF_SWITCHES): - vol.All(cv.ensure_list, [vol.In(SWITCHES)]), - })]) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.All( + cv.ensure_list, + [ + vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional( + CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION + ): vol.All(vol.In(AUTHENTICATION_LIST)), + vol.Optional( + CONF_RESOLUTION, default=DEFAULT_RESOLUTION + ): vol.All(vol.In(RESOLUTION_LIST)), + vol.Optional( + CONF_STREAM_SOURCE, default=DEFAULT_STREAM_SOURCE + ): vol.All(vol.In(STREAM_SOURCE_LIST)), + vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string, + vol.Optional( + CONF_SCAN_INTERVAL, default=SCAN_INTERVAL + ): cv.time_period, + vol.Optional(CONF_SENSORS): vol.All( + cv.ensure_list, [vol.In(SENSORS)] + ), + vol.Optional(CONF_SWITCHES): vol.All( + cv.ensure_list, [vol.In(SWITCHES)] + ), + } + ) + ], + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -103,21 +118,24 @@ def setup(hass, config): for device in amcrest_cams: try: - camera = AmcrestCamera(device.get(CONF_HOST), - device.get(CONF_PORT), - device.get(CONF_USERNAME), - device.get(CONF_PASSWORD)).camera + camera = AmcrestCamera( + device.get(CONF_HOST), + device.get(CONF_PORT), + device.get(CONF_USERNAME), + device.get(CONF_PASSWORD), + ).camera # pylint: disable=pointless-statement camera.current_time except (ConnectError, ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Amcrest camera: %s", str(ex)) hass.components.persistent_notification.create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) continue ffmpeg_arguments = device.get(CONF_FFMPEG_ARGUMENTS) @@ -139,27 +157,24 @@ def setup(hass, config): authentication = None hass.data[DATA_AMCREST][name] = AmcrestDevice( - camera, name, authentication, ffmpeg_arguments, stream_source, - resolution) + camera, name, authentication, ffmpeg_arguments, stream_source, resolution + ) - discovery.load_platform( - hass, 'camera', DOMAIN, { - CONF_NAME: name, - }, config) + discovery.load_platform(hass, "camera", DOMAIN, {CONF_NAME: name}, config) if sensors: discovery.load_platform( - hass, 'sensor', DOMAIN, { - CONF_NAME: name, - CONF_SENSORS: sensors, - }, config) + hass, "sensor", DOMAIN, {CONF_NAME: name, CONF_SENSORS: sensors}, config + ) if switches: discovery.load_platform( - hass, 'switch', DOMAIN, { - CONF_NAME: name, - CONF_SWITCHES: switches - }, config) + hass, + "switch", + DOMAIN, + {CONF_NAME: name, CONF_SWITCHES: switches}, + config, + ) return True @@ -167,8 +182,9 @@ def setup(hass, config): class AmcrestDevice: """Representation of a base Amcrest discovery device.""" - def __init__(self, camera, name, authentication, ffmpeg_arguments, - stream_source, resolution): + def __init__( + self, camera, name, authentication, ffmpeg_arguments, stream_source, resolution + ): """Initialize the entity.""" self.device = camera self.name = name diff --git a/homeassistant/components/android_ip_webcam.py b/homeassistant/components/android_ip_webcam.py index 5da117e74..58795d316 100644 --- a/homeassistant/components/android_ip_webcam.py +++ b/homeassistant/components/android_ip_webcam.py @@ -12,141 +12,183 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.const import ( - CONF_NAME, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD, - CONF_SENSORS, CONF_SWITCHES, CONF_TIMEOUT, CONF_SCAN_INTERVAL, - CONF_PLATFORM) + CONF_NAME, + CONF_HOST, + CONF_PORT, + CONF_USERNAME, + CONF_PASSWORD, + CONF_SENSORS, + CONF_SWITCHES, + CONF_TIMEOUT, + CONF_SCAN_INTERVAL, + CONF_PLATFORM, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( - async_dispatcher_send, async_dispatcher_connect) + async_dispatcher_send, + async_dispatcher_connect, +) from homeassistant.helpers.entity import Entity from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.util.dt import utcnow -from homeassistant.components.camera.mjpeg import ( - CONF_MJPEG_URL, CONF_STILL_IMAGE_URL) +from homeassistant.components.camera.mjpeg import CONF_MJPEG_URL, CONF_STILL_IMAGE_URL -REQUIREMENTS = ['pydroid-ipcam==0.8'] +REQUIREMENTS = ["pydroid-ipcam==0.8"] _LOGGER = logging.getLogger(__name__) -ATTR_AUD_CONNS = 'Audio Connections' -ATTR_HOST = 'host' -ATTR_VID_CONNS = 'Video Connections' +ATTR_AUD_CONNS = "Audio Connections" +ATTR_HOST = "host" +ATTR_VID_CONNS = "Video Connections" -CONF_MOTION_SENSOR = 'motion_sensor' +CONF_MOTION_SENSOR = "motion_sensor" -DATA_IP_WEBCAM = 'android_ip_webcam' -DEFAULT_NAME = 'IP Webcam' +DATA_IP_WEBCAM = "android_ip_webcam" +DEFAULT_NAME = "IP Webcam" DEFAULT_PORT = 8080 DEFAULT_TIMEOUT = 10 -DOMAIN = 'android_ip_webcam' +DOMAIN = "android_ip_webcam" SCAN_INTERVAL = timedelta(seconds=10) -SIGNAL_UPDATE_DATA = 'android_ip_webcam_update' +SIGNAL_UPDATE_DATA = "android_ip_webcam_update" KEY_MAP = { - 'audio_connections': 'Audio Connections', - 'adet_limit': 'Audio Trigger Limit', - 'antibanding': 'Anti-banding', - 'audio_only': 'Audio Only', - 'battery_level': 'Battery Level', - 'battery_temp': 'Battery Temperature', - 'battery_voltage': 'Battery Voltage', - 'coloreffect': 'Color Effect', - 'exposure': 'Exposure Level', - 'exposure_lock': 'Exposure Lock', - 'ffc': 'Front-facing Camera', - 'flashmode': 'Flash Mode', - 'focus': 'Focus', - 'focus_homing': 'Focus Homing', - 'focus_region': 'Focus Region', - 'focusmode': 'Focus Mode', - 'gps_active': 'GPS Active', - 'idle': 'Idle', - 'ip_address': 'IPv4 Address', - 'ipv6_address': 'IPv6 Address', - 'ivideon_streaming': 'Ivideon Streaming', - 'light': 'Light Level', - 'mirror_flip': 'Mirror Flip', - 'motion': 'Motion', - 'motion_active': 'Motion Active', - 'motion_detect': 'Motion Detection', - 'motion_event': 'Motion Event', - 'motion_limit': 'Motion Limit', - 'night_vision': 'Night Vision', - 'night_vision_average': 'Night Vision Average', - 'night_vision_gain': 'Night Vision Gain', - 'orientation': 'Orientation', - 'overlay': 'Overlay', - 'photo_size': 'Photo Size', - 'pressure': 'Pressure', - 'proximity': 'Proximity', - 'quality': 'Quality', - 'scenemode': 'Scene Mode', - 'sound': 'Sound', - 'sound_event': 'Sound Event', - 'sound_timeout': 'Sound Timeout', - 'torch': 'Torch', - 'video_connections': 'Video Connections', - 'video_chunk_len': 'Video Chunk Length', - 'video_recording': 'Video Recording', - 'video_size': 'Video Size', - 'whitebalance': 'White Balance', - 'whitebalance_lock': 'White Balance Lock', - 'zoom': 'Zoom' + "audio_connections": "Audio Connections", + "adet_limit": "Audio Trigger Limit", + "antibanding": "Anti-banding", + "audio_only": "Audio Only", + "battery_level": "Battery Level", + "battery_temp": "Battery Temperature", + "battery_voltage": "Battery Voltage", + "coloreffect": "Color Effect", + "exposure": "Exposure Level", + "exposure_lock": "Exposure Lock", + "ffc": "Front-facing Camera", + "flashmode": "Flash Mode", + "focus": "Focus", + "focus_homing": "Focus Homing", + "focus_region": "Focus Region", + "focusmode": "Focus Mode", + "gps_active": "GPS Active", + "idle": "Idle", + "ip_address": "IPv4 Address", + "ipv6_address": "IPv6 Address", + "ivideon_streaming": "Ivideon Streaming", + "light": "Light Level", + "mirror_flip": "Mirror Flip", + "motion": "Motion", + "motion_active": "Motion Active", + "motion_detect": "Motion Detection", + "motion_event": "Motion Event", + "motion_limit": "Motion Limit", + "night_vision": "Night Vision", + "night_vision_average": "Night Vision Average", + "night_vision_gain": "Night Vision Gain", + "orientation": "Orientation", + "overlay": "Overlay", + "photo_size": "Photo Size", + "pressure": "Pressure", + "proximity": "Proximity", + "quality": "Quality", + "scenemode": "Scene Mode", + "sound": "Sound", + "sound_event": "Sound Event", + "sound_timeout": "Sound Timeout", + "torch": "Torch", + "video_connections": "Video Connections", + "video_chunk_len": "Video Chunk Length", + "video_recording": "Video Recording", + "video_size": "Video Size", + "whitebalance": "White Balance", + "whitebalance_lock": "White Balance Lock", + "zoom": "Zoom", } ICON_MAP = { - 'audio_connections': 'mdi:speaker', - 'battery_level': 'mdi:battery', - 'battery_temp': 'mdi:thermometer', - 'battery_voltage': 'mdi:battery-charging-100', - 'exposure_lock': 'mdi:camera', - 'ffc': 'mdi:camera-front-variant', - 'focus': 'mdi:image-filter-center-focus', - 'gps_active': 'mdi:crosshairs-gps', - 'light': 'mdi:flashlight', - 'motion': 'mdi:run', - 'night_vision': 'mdi:weather-night', - 'overlay': 'mdi:monitor', - 'pressure': 'mdi:gauge', - 'proximity': 'mdi:map-marker-radius', - 'quality': 'mdi:quality-high', - 'sound': 'mdi:speaker', - 'sound_event': 'mdi:speaker', - 'sound_timeout': 'mdi:speaker', - 'torch': 'mdi:white-balance-sunny', - 'video_chunk_len': 'mdi:video', - 'video_connections': 'mdi:eye', - 'video_recording': 'mdi:record-rec', - 'whitebalance_lock': 'mdi:white-balance-auto' + "audio_connections": "mdi:speaker", + "battery_level": "mdi:battery", + "battery_temp": "mdi:thermometer", + "battery_voltage": "mdi:battery-charging-100", + "exposure_lock": "mdi:camera", + "ffc": "mdi:camera-front-variant", + "focus": "mdi:image-filter-center-focus", + "gps_active": "mdi:crosshairs-gps", + "light": "mdi:flashlight", + "motion": "mdi:run", + "night_vision": "mdi:weather-night", + "overlay": "mdi:monitor", + "pressure": "mdi:gauge", + "proximity": "mdi:map-marker-radius", + "quality": "mdi:quality-high", + "sound": "mdi:speaker", + "sound_event": "mdi:speaker", + "sound_timeout": "mdi:speaker", + "torch": "mdi:white-balance-sunny", + "video_chunk_len": "mdi:video", + "video_connections": "mdi:eye", + "video_recording": "mdi:record-rec", + "whitebalance_lock": "mdi:white-balance-auto", } -SWITCHES = ['exposure_lock', 'ffc', 'focus', 'gps_active', 'night_vision', - 'overlay', 'torch', 'whitebalance_lock', 'video_recording'] +SWITCHES = [ + "exposure_lock", + "ffc", + "focus", + "gps_active", + "night_vision", + "overlay", + "torch", + "whitebalance_lock", + "video_recording", +] -SENSORS = ['audio_connections', 'battery_level', 'battery_temp', - 'battery_voltage', 'light', 'motion', 'pressure', 'proximity', - 'sound', 'video_connections'] +SENSORS = [ + "audio_connections", + "battery_level", + "battery_temp", + "battery_voltage", + "light", + "motion", + "pressure", + "proximity", + "sound", + "video_connections", +] -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.All(cv.ensure_list, [vol.Schema({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): - cv.time_period, - vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string, - vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string, - vol.Optional(CONF_SWITCHES): - vol.All(cv.ensure_list, [vol.In(SWITCHES)]), - vol.Optional(CONF_SENSORS): - vol.All(cv.ensure_list, [vol.In(SENSORS)]), - vol.Optional(CONF_MOTION_SENSOR): cv.boolean, - })]) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.All( + cv.ensure_list, + [ + vol.Schema( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional( + CONF_TIMEOUT, default=DEFAULT_TIMEOUT + ): cv.positive_int, + vol.Optional( + CONF_SCAN_INTERVAL, default=SCAN_INTERVAL + ): cv.time_period, + vol.Inclusive(CONF_USERNAME, "authentication"): cv.string, + vol.Inclusive(CONF_PASSWORD, "authentication"): cv.string, + vol.Optional(CONF_SWITCHES): vol.All( + cv.ensure_list, [vol.In(SWITCHES)] + ), + vol.Optional(CONF_SENSORS): vol.All( + cv.ensure_list, [vol.In(SENSORS)] + ), + vol.Optional(CONF_MOTION_SENSOR): cv.boolean, + } + ) + ], + ) + }, + extra=vol.ALLOW_EXTRA, +) @asyncio.coroutine @@ -171,22 +213,26 @@ def async_setup(hass, config): # Init ip webcam cam = PyDroidIPCam( - hass.loop, websession, host, cam_config[CONF_PORT], - username=username, password=password, - timeout=cam_config[CONF_TIMEOUT] + hass.loop, + websession, + host, + cam_config[CONF_PORT], + username=username, + password=password, + timeout=cam_config[CONF_TIMEOUT], ) if switches is None: - switches = [setting for setting in cam.enabled_settings - if setting in SWITCHES] + switches = [ + setting for setting in cam.enabled_settings if setting in SWITCHES + ] if sensors is None: - sensors = [sensor for sensor in cam.enabled_sensors - if sensor in SENSORS] - sensors.extend(['audio_connections', 'video_connections']) + sensors = [sensor for sensor in cam.enabled_sensors if sensor in SENSORS] + sensors.extend(["audio_connections", "video_connections"]) if motion is None: - motion = 'motion_active' in cam.enabled_sensors + motion = "motion_active" in cam.enabled_sensors @asyncio.coroutine def async_update_data(now): @@ -194,8 +240,7 @@ def async_setup(hass, config): yield from cam.update() async_dispatcher_send(hass, SIGNAL_UPDATE_DATA, host) - async_track_point_in_utc_time( - hass, async_update_data, utcnow() + interval) + async_track_point_in_utc_time(hass, async_update_data, utcnow() + interval) yield from async_update_data(None) @@ -203,42 +248,50 @@ def async_setup(hass, config): webcams[host] = cam mjpeg_camera = { - CONF_PLATFORM: 'mjpeg', + CONF_PLATFORM: "mjpeg", CONF_MJPEG_URL: cam.mjpeg_url, CONF_STILL_IMAGE_URL: cam.image_url, CONF_NAME: name, } if username and password: - mjpeg_camera.update({ - CONF_USERNAME: username, - CONF_PASSWORD: password - }) + mjpeg_camera.update({CONF_USERNAME: username, CONF_PASSWORD: password}) - hass.async_create_task(discovery.async_load_platform( - hass, 'camera', 'mjpeg', mjpeg_camera, config)) + hass.async_create_task( + discovery.async_load_platform(hass, "camera", "mjpeg", mjpeg_camera, config) + ) if sensors: - hass.async_create_task(discovery.async_load_platform( - hass, 'sensor', DOMAIN, { - CONF_NAME: name, - CONF_HOST: host, - CONF_SENSORS: sensors, - }, config)) + hass.async_create_task( + discovery.async_load_platform( + hass, + "sensor", + DOMAIN, + {CONF_NAME: name, CONF_HOST: host, CONF_SENSORS: sensors}, + config, + ) + ) if switches: - hass.async_create_task(discovery.async_load_platform( - hass, 'switch', DOMAIN, { - CONF_NAME: name, - CONF_HOST: host, - CONF_SWITCHES: switches, - }, config)) + hass.async_create_task( + discovery.async_load_platform( + hass, + "switch", + DOMAIN, + {CONF_NAME: name, CONF_HOST: host, CONF_SWITCHES: switches}, + config, + ) + ) if motion: - hass.async_create_task(discovery.async_load_platform( - hass, 'binary_sensor', DOMAIN, { - CONF_HOST: host, - CONF_NAME: name, - }, config)) + hass.async_create_task( + discovery.async_load_platform( + hass, + "binary_sensor", + DOMAIN, + {CONF_HOST: host, CONF_NAME: name}, + config, + ) + ) tasks = [async_setup_ipcamera(conf) for conf in config[DOMAIN]] if tasks: @@ -258,6 +311,7 @@ class AndroidIPCamEntity(Entity): @asyncio.coroutine def async_added_to_hass(self): """Register update dispatcher.""" + @callback def async_ipcam_update(host): """Update callback.""" @@ -265,8 +319,7 @@ class AndroidIPCamEntity(Entity): return self.async_schedule_update_ha_state(True) - async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_DATA, async_ipcam_update) + async_dispatcher_connect(self.hass, SIGNAL_UPDATE_DATA, async_ipcam_update) @property def should_poll(self): @@ -285,9 +338,7 @@ class AndroidIPCamEntity(Entity): if self._ipcam.status_data is None: return state_attr - state_attr[ATTR_VID_CONNS] = \ - self._ipcam.status_data.get('video_connections') - state_attr[ATTR_AUD_CONNS] = \ - self._ipcam.status_data.get('audio_connections') + state_attr[ATTR_VID_CONNS] = self._ipcam.status_data.get("video_connections") + state_attr[ATTR_AUD_CONNS] = self._ipcam.status_data.get("audio_connections") return state_attr diff --git a/homeassistant/components/apcupsd.py b/homeassistant/components/apcupsd.py index 8808cee79..6491e0806 100644 --- a/homeassistant/components/apcupsd.py +++ b/homeassistant/components/apcupsd.py @@ -9,33 +9,38 @@ from datetime import timedelta import voluptuous as vol -from homeassistant.const import (CONF_HOST, CONF_PORT) +from homeassistant.const import CONF_HOST, CONF_PORT import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle -REQUIREMENTS = ['apcaccess==0.0.13'] +REQUIREMENTS = ["apcaccess==0.0.13"] _LOGGER = logging.getLogger(__name__) -CONF_TYPE = 'type' +CONF_TYPE = "type" DATA = None -DEFAULT_HOST = 'localhost' +DEFAULT_HOST = "localhost" DEFAULT_PORT = 3551 -DOMAIN = 'apcupsd' +DOMAIN = "apcupsd" -KEY_STATUS = 'STATUS' +KEY_STATUS = "STATUS" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) -VALUE_ONLINE = 'ONLINE' +VALUE_ONLINE = "ONLINE" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -68,6 +73,7 @@ class APCUPSdData: def __init__(self, host, port): """Initialize the data object.""" from apcaccess import status + self._host = host self._port = port self._status = None diff --git a/homeassistant/components/api.py b/homeassistant/components/api.py index 0fbb4de39..5daed4479 100644 --- a/homeassistant/components/api.py +++ b/homeassistant/components/api.py @@ -14,11 +14,25 @@ import async_timeout from homeassistant.bootstrap import DATA_LOGGING from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( - EVENT_HOMEASSISTANT_STOP, EVENT_TIME_CHANGED, HTTP_BAD_REQUEST, - HTTP_CREATED, HTTP_NOT_FOUND, MATCH_ALL, URL_API, URL_API_COMPONENTS, - URL_API_CONFIG, URL_API_DISCOVERY_INFO, URL_API_ERROR_LOG, URL_API_EVENTS, - URL_API_SERVICES, URL_API_STATES, URL_API_STATES_ENTITY, URL_API_STREAM, - URL_API_TEMPLATE, __version__) + EVENT_HOMEASSISTANT_STOP, + EVENT_TIME_CHANGED, + HTTP_BAD_REQUEST, + HTTP_CREATED, + HTTP_NOT_FOUND, + MATCH_ALL, + URL_API, + URL_API_COMPONENTS, + URL_API_CONFIG, + URL_API_DISCOVERY_INFO, + URL_API_ERROR_LOG, + URL_API_EVENTS, + URL_API_SERVICES, + URL_API_STATES, + URL_API_STATES_ENTITY, + URL_API_STREAM, + URL_API_TEMPLATE, + __version__, +) import homeassistant.core as ha from homeassistant.exceptions import TemplateError from homeassistant.helpers import template @@ -28,15 +42,15 @@ from homeassistant.helpers.json import JSONEncoder _LOGGER = logging.getLogger(__name__) -ATTR_BASE_URL = 'base_url' -ATTR_LOCATION_NAME = 'location_name' -ATTR_REQUIRES_API_PASSWORD = 'requires_api_password' -ATTR_VERSION = 'version' +ATTR_BASE_URL = "base_url" +ATTR_LOCATION_NAME = "location_name" +ATTR_REQUIRES_API_PASSWORD = "requires_api_password" +ATTR_VERSION = "version" -DOMAIN = 'api' -DEPENDENCIES = ['http'] +DOMAIN = "api" +DEPENDENCIES = ["http"] -STREAM_PING_PAYLOAD = 'ping' +STREAM_PING_PAYLOAD = "ping" STREAM_PING_INTERVAL = 50 # seconds @@ -65,7 +79,7 @@ class APIStatusView(HomeAssistantView): """View to handle Status requests.""" url = URL_API - name = 'api:status' + name = "api:status" @ha.callback def get(self, request): @@ -77,17 +91,17 @@ class APIEventStream(HomeAssistantView): """View to handle EventStream requests.""" url = URL_API_STREAM - name = 'api:stream' + name = "api:stream" async def get(self, request): """Provide a streaming interface for the event bus.""" - hass = request.app['hass'] + hass = request.app["hass"] stop_obj = object() to_write = asyncio.Queue(loop=hass.loop) - restrict = request.query.get('restrict') + restrict = request.query.get("restrict") if restrict: - restrict = restrict.split(',') + [EVENT_HOMEASSISTANT_STOP] + restrict = restrict.split(",") + [EVENT_HOMEASSISTANT_STOP] async def forward_events(event): """Forward events to the open request.""" @@ -107,7 +121,7 @@ class APIEventStream(HomeAssistantView): await to_write.put(data) response = web.StreamResponse() - response.content_type = 'text/event-stream' + response.content_type = "text/event-stream" await response.prepare(request) unsub_stream = hass.bus.async_listen(MATCH_ALL, forward_events) @@ -120,17 +134,15 @@ class APIEventStream(HomeAssistantView): while True: try: - with async_timeout.timeout(STREAM_PING_INTERVAL, - loop=hass.loop): + with async_timeout.timeout(STREAM_PING_INTERVAL, loop=hass.loop): payload = await to_write.get() if payload is stop_obj: break msg = "data: {}\n\n".format(payload) - _LOGGER.debug( - "STREAM %s WRITING %s", id(stop_obj), msg.strip()) - await response.write(msg.encode('UTF-8')) + _LOGGER.debug("STREAM %s WRITING %s", id(stop_obj), msg.strip()) + await response.write(msg.encode("UTF-8")) except asyncio.TimeoutError: await to_write.put(STREAM_PING_PAYLOAD) @@ -146,12 +158,12 @@ class APIConfigView(HomeAssistantView): """View to handle Configuration requests.""" url = URL_API_CONFIG - name = 'api:config' + name = "api:config" @ha.callback def get(self, request): """Get current configuration.""" - return self.json(request.app['hass'].config.as_dict()) + return self.json(request.app["hass"].config.as_dict()) class APIDiscoveryView(HomeAssistantView): @@ -159,19 +171,21 @@ class APIDiscoveryView(HomeAssistantView): requires_auth = False url = URL_API_DISCOVERY_INFO - name = 'api:discovery' + name = "api:discovery" @ha.callback def get(self, request): """Get discovery information.""" - hass = request.app['hass'] + hass = request.app["hass"] needs_auth = hass.config.api.api_password is not None - return self.json({ - ATTR_BASE_URL: hass.config.api.base_url, - ATTR_LOCATION_NAME: hass.config.location_name, - ATTR_REQUIRES_API_PASSWORD: needs_auth, - ATTR_VERSION: __version__, - }) + return self.json( + { + ATTR_BASE_URL: hass.config.api.base_url, + ATTR_LOCATION_NAME: hass.config.location_name, + ATTR_REQUIRES_API_PASSWORD: needs_auth, + ATTR_VERSION: __version__, + } + ) class APIStatesView(HomeAssistantView): @@ -183,58 +197,58 @@ class APIStatesView(HomeAssistantView): @ha.callback def get(self, request): """Get current states.""" - return self.json(request.app['hass'].states.async_all()) + return self.json(request.app["hass"].states.async_all()) class APIEntityStateView(HomeAssistantView): """View to handle EntityState requests.""" - url = '/api/states/{entity_id}' - name = 'api:entity-state' + url = "/api/states/{entity_id}" + name = "api:entity-state" @ha.callback def get(self, request, entity_id): """Retrieve state of entity.""" - state = request.app['hass'].states.get(entity_id) + state = request.app["hass"].states.get(entity_id) if state: return self.json(state) return self.json_message("Entity not found.", HTTP_NOT_FOUND) async def post(self, request, entity_id): """Update state of entity.""" - hass = request.app['hass'] + hass = request.app["hass"] try: data = await request.json() except ValueError: - return self.json_message( - "Invalid JSON specified.", HTTP_BAD_REQUEST) + return self.json_message("Invalid JSON specified.", HTTP_BAD_REQUEST) - new_state = data.get('state') + new_state = data.get("state") if new_state is None: return self.json_message("No state specified.", HTTP_BAD_REQUEST) - attributes = data.get('attributes') - force_update = data.get('force_update', False) + attributes = data.get("attributes") + force_update = data.get("force_update", False) is_new_state = hass.states.get(entity_id) is None # Write state - hass.states.async_set(entity_id, new_state, attributes, force_update, - self.context(request)) + hass.states.async_set( + entity_id, new_state, attributes, force_update, self.context(request) + ) # Read the state back for our response status_code = HTTP_CREATED if is_new_state else 200 resp = self.json(hass.states.get(entity_id), status_code) - resp.headers.add('Location', URL_API_STATES_ENTITY.format(entity_id)) + resp.headers.add("Location", URL_API_STATES_ENTITY.format(entity_id)) return resp @ha.callback def delete(self, request, entity_id): """Remove entity.""" - if request.app['hass'].states.async_remove(entity_id): + if request.app["hass"].states.async_remove(entity_id): return self.json_message("Entity removed.") return self.json_message("Entity not found.", HTTP_NOT_FOUND) @@ -243,19 +257,19 @@ class APIEventListenersView(HomeAssistantView): """View to handle EventListeners requests.""" url = URL_API_EVENTS - name = 'api:event-listeners' + name = "api:event-listeners" @ha.callback def get(self, request): """Get event listeners.""" - return self.json(async_events_json(request.app['hass'])) + return self.json(async_events_json(request.app["hass"])) class APIEventView(HomeAssistantView): """View to handle Event requests.""" - url = '/api/events/{event_type}' - name = 'api:event' + url = "/api/events/{event_type}" + name = "api:event" async def post(self, request, event_type): """Fire events.""" @@ -264,24 +278,26 @@ class APIEventView(HomeAssistantView): event_data = json.loads(body) if body else None except ValueError: return self.json_message( - "Event data should be valid JSON.", HTTP_BAD_REQUEST) + "Event data should be valid JSON.", HTTP_BAD_REQUEST + ) if event_data is not None and not isinstance(event_data, dict): return self.json_message( - "Event data should be a JSON object", HTTP_BAD_REQUEST) + "Event data should be a JSON object", HTTP_BAD_REQUEST + ) # Special case handling for event STATE_CHANGED # We will try to convert state dicts back to State objects if event_type == ha.EVENT_STATE_CHANGED and event_data: - for key in ('old_state', 'new_state'): + for key in ("old_state", "new_state"): state = ha.State.from_dict(event_data.get(key)) if state: event_data[key] = state - request.app['hass'].bus.async_fire( - event_type, event_data, ha.EventOrigin.remote, - self.context(request)) + request.app["hass"].bus.async_fire( + event_type, event_data, ha.EventOrigin.remote, self.context(request) + ) return self.json_message("Event {} fired.".format(event_type)) @@ -290,36 +306,36 @@ class APIServicesView(HomeAssistantView): """View to handle Services requests.""" url = URL_API_SERVICES - name = 'api:services' + name = "api:services" async def get(self, request): """Get registered services.""" - services = await async_services_json(request.app['hass']) + services = await async_services_json(request.app["hass"]) return self.json(services) class APIDomainServicesView(HomeAssistantView): """View to handle DomainServices requests.""" - url = '/api/services/{domain}/{service}' - name = 'api:domain-services' + url = "/api/services/{domain}/{service}" + name = "api:domain-services" async def post(self, request, domain, service): """Call a service. Returns a list of changed states. """ - hass = request.app['hass'] + hass = request.app["hass"] body = await request.text() try: data = json.loads(body) if body else None except ValueError: - return self.json_message( - "Data should be valid JSON.", HTTP_BAD_REQUEST) + return self.json_message("Data should be valid JSON.", HTTP_BAD_REQUEST) with AsyncTrackStates(hass) as changed_states: await hass.services.async_call( - domain, service, data, True, self.context(request)) + domain, service, data, True, self.context(request) + ) return self.json(changed_states) @@ -328,50 +344,52 @@ class APIComponentsView(HomeAssistantView): """View to handle Components requests.""" url = URL_API_COMPONENTS - name = 'api:components' + name = "api:components" @ha.callback def get(self, request): """Get current loaded components.""" - return self.json(request.app['hass'].config.components) + return self.json(request.app["hass"].config.components) class APITemplateView(HomeAssistantView): """View to handle Template requests.""" url = URL_API_TEMPLATE - name = 'api:template' + name = "api:template" async def post(self, request): """Render a template.""" try: data = await request.json() - tpl = template.Template(data['template'], request.app['hass']) - return tpl.async_render(data.get('variables')) + tpl = template.Template(data["template"], request.app["hass"]) + return tpl.async_render(data.get("variables")) except (ValueError, TemplateError) as ex: return self.json_message( - "Error rendering template: {}".format(ex), HTTP_BAD_REQUEST) + "Error rendering template: {}".format(ex), HTTP_BAD_REQUEST + ) class APIErrorLog(HomeAssistantView): """View to fetch the API error log.""" url = URL_API_ERROR_LOG - name = 'api:error_log' + name = "api:error_log" async def get(self, request): """Retrieve API error log.""" - return web.FileResponse(request.app['hass'].data[DATA_LOGGING]) + return web.FileResponse(request.app["hass"].data[DATA_LOGGING]) async def async_services_json(hass): """Generate services data to JSONify.""" descriptions = await async_get_all_descriptions(hass) - return [{'domain': key, 'services': value} - for key, value in descriptions.items()] + return [{"domain": key, "services": value} for key, value in descriptions.items()] def async_events_json(hass): """Generate event data to JSONify.""" - return [{'event': key, 'listener_count': value} - for key, value in hass.bus.async_listeners().items()] + return [ + {"event": key, "listener_count": value} + for key, value in hass.bus.async_listeners().items() + ] diff --git a/homeassistant/components/apple_tv.py b/homeassistant/components/apple_tv.py index 21ff0e328..1b0010dde 100644 --- a/homeassistant/components/apple_tv.py +++ b/homeassistant/components/apple_tv.py @@ -16,35 +16,35 @@ from homeassistant.helpers import discovery from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyatv==0.3.10'] +REQUIREMENTS = ["pyatv==0.3.10"] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'apple_tv' +DOMAIN = "apple_tv" -SERVICE_SCAN = 'apple_tv_scan' -SERVICE_AUTHENTICATE = 'apple_tv_authenticate' +SERVICE_SCAN = "apple_tv_scan" +SERVICE_AUTHENTICATE = "apple_tv_authenticate" -ATTR_ATV = 'atv' -ATTR_POWER = 'power' +ATTR_ATV = "atv" +ATTR_POWER = "power" -CONF_LOGIN_ID = 'login_id' -CONF_START_OFF = 'start_off' -CONF_CREDENTIALS = 'credentials' +CONF_LOGIN_ID = "login_id" +CONF_START_OFF = "start_off" +CONF_CREDENTIALS = "credentials" -DEFAULT_NAME = 'Apple TV' +DEFAULT_NAME = "Apple TV" -DATA_APPLE_TV = 'data_apple_tv' -DATA_ENTITIES = 'data_apple_tv_entities' +DATA_APPLE_TV = "data_apple_tv" +DATA_ENTITIES = "data_apple_tv_entities" -KEY_CONFIG = 'apple_tv_configuring' +KEY_CONFIG = "apple_tv_configuring" -NOTIFICATION_AUTH_ID = 'apple_tv_auth_notification' -NOTIFICATION_AUTH_TITLE = 'Apple TV Authentication' -NOTIFICATION_SCAN_ID = 'apple_tv_scan_notification' -NOTIFICATION_SCAN_TITLE = 'Apple TV Scan' +NOTIFICATION_AUTH_ID = "apple_tv_auth_notification" +NOTIFICATION_AUTH_TITLE = "Apple TV Authentication" +NOTIFICATION_SCAN_ID = "apple_tv_scan_notification" +NOTIFICATION_SCAN_TITLE = "Apple TV Scan" -T = TypeVar('T') # pylint: disable=invalid-name +T = TypeVar("T") # pylint: disable=invalid-name # This version of ensure_list interprets an empty dict as no value @@ -55,22 +55,30 @@ def ensure_list(value: Union[T, Sequence[T]]) -> Sequence[T]: return value if isinstance(value, list) else [value] -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.All(ensure_list, [vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_LOGIN_ID): cv.string, - vol.Optional(CONF_CREDENTIALS): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_START_OFF, default=False): cv.boolean, - })]) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.All( + ensure_list, + [ + vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_LOGIN_ID): cv.string, + vol.Optional(CONF_CREDENTIALS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_START_OFF, default=False): cv.boolean, + } + ) + ], + ) + }, + extra=vol.ALLOW_EXTRA, +) # Currently no attributes but it might change later APPLE_TV_SCAN_SCHEMA = vol.Schema({}) -APPLE_TV_AUTHENTICATE_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids, -}) +APPLE_TV_AUTHENTICATE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids}) def request_configuration(hass, config, atv, credentials): @@ -81,30 +89,34 @@ def request_configuration(hass, config, atv, credentials): def configuration_callback(callback_data): """Handle the submitted configuration.""" from pyatv import exceptions - pin = callback_data.get('pin') + + pin = callback_data.get("pin") try: yield from atv.airplay.finish_authentication(pin) hass.components.persistent_notification.async_create( - 'Authentication succeeded!

Add the following ' - 'to credentials: in your apple_tv configuration:

' - '{0}'.format(credentials), + "Authentication succeeded!

Add the following " + "to credentials: in your apple_tv configuration:

" + "{0}".format(credentials), title=NOTIFICATION_AUTH_TITLE, - notification_id=NOTIFICATION_AUTH_ID) + notification_id=NOTIFICATION_AUTH_ID, + ) except exceptions.DeviceAuthenticationError as ex: hass.components.persistent_notification.async_create( - 'Authentication failed! Did you enter correct PIN?

' - 'Details: {0}'.format(ex), + "Authentication failed! Did you enter correct PIN?

" + "Details: {0}".format(ex), title=NOTIFICATION_AUTH_TITLE, - notification_id=NOTIFICATION_AUTH_ID) + notification_id=NOTIFICATION_AUTH_ID, + ) hass.async_add_job(configurator.request_done, instance) instance = configurator.request_config( - 'Apple TV Authentication', configuration_callback, - description='Please enter PIN code shown on screen.', - submit_caption='Confirm', - fields=[{'id': 'pin', 'name': 'PIN Code', 'type': 'password'}] + "Apple TV Authentication", + configuration_callback, + description="Please enter PIN code shown on screen.", + submit_caption="Confirm", + fields=[{"id": "pin", "name": "PIN Code", "type": "password"}], ) @@ -112,24 +124,28 @@ def request_configuration(hass, config, atv, credentials): def scan_for_apple_tvs(hass): """Scan for devices and present a notification of the ones found.""" import pyatv + atvs = yield from pyatv.scan_for_apple_tvs(hass.loop, timeout=3) devices = [] for atv in atvs: login_id = atv.login_id if login_id is None: - login_id = 'Home Sharing disabled' - devices.append('Name: {0}
Host: {1}
Login ID: {2}'.format( - atv.name, atv.address, login_id)) + login_id = "Home Sharing disabled" + devices.append( + "Name: {0}
Host: {1}
Login ID: {2}".format( + atv.name, atv.address, login_id + ) + ) if not devices: - devices = ['No device(s) found'] + devices = ["No device(s) found"] hass.components.persistent_notification.async_create( - 'The following devices were found:

' + - '

'.join(devices), + "The following devices were found:

" + "

".join(devices), title=NOTIFICATION_SCAN_TITLE, - notification_id=NOTIFICATION_SCAN_ID) + notification_id=NOTIFICATION_SCAN_ID, + ) @asyncio.coroutine @@ -148,8 +164,11 @@ def async_setup(hass, config): return if entity_ids: - devices = [device for device in hass.data[DATA_ENTITIES] - if device.entity_id in entity_ids] + devices = [ + device + for device in hass.data[DATA_ENTITIES] + if device.entity_id in entity_ids + ] else: devices = hass.data[DATA_ENTITIES] @@ -160,20 +179,22 @@ def async_setup(hass, config): atv = device.atv credentials = yield from atv.airplay.generate_credentials() yield from atv.airplay.load_credentials(credentials) - _LOGGER.debug('Generated new credentials: %s', credentials) + _LOGGER.debug("Generated new credentials: %s", credentials) yield from atv.airplay.start_authentication() - hass.async_add_job(request_configuration, - hass, config, atv, credentials) + hass.async_add_job(request_configuration, hass, config, atv, credentials) @asyncio.coroutine def atv_discovered(service, info): """Set up an Apple TV that was auto discovered.""" - yield from _setup_atv(hass, { - CONF_NAME: info['name'], - CONF_HOST: info['host'], - CONF_LOGIN_ID: info['properties']['hG'], - CONF_START_OFF: False - }) + yield from _setup_atv( + hass, + { + CONF_NAME: info["name"], + CONF_HOST: info["host"], + CONF_LOGIN_ID: info["properties"]["hG"], + CONF_START_OFF: False, + }, + ) discovery.async_listen(hass, SERVICE_APPLE_TV, atv_discovered) @@ -182,12 +203,15 @@ def async_setup(hass, config): yield from asyncio.wait(tasks, loop=hass.loop) hass.services.async_register( - DOMAIN, SERVICE_SCAN, async_service_handler, - schema=APPLE_TV_SCAN_SCHEMA) + DOMAIN, SERVICE_SCAN, async_service_handler, schema=APPLE_TV_SCAN_SCHEMA + ) hass.services.async_register( - DOMAIN, SERVICE_AUTHENTICATE, async_service_handler, - schema=APPLE_TV_AUTHENTICATE_SCHEMA) + DOMAIN, + SERVICE_AUTHENTICATE, + async_service_handler, + schema=APPLE_TV_AUTHENTICATE_SCHEMA, + ) return True @@ -196,6 +220,7 @@ def async_setup(hass, config): def _setup_atv(hass, atv_config): """Set up an Apple TV.""" import pyatv + name = atv_config.get(CONF_NAME) host = atv_config.get(CONF_HOST) login_id = atv_config.get(CONF_LOGIN_ID) @@ -212,16 +237,15 @@ def _setup_atv(hass, atv_config): yield from atv.airplay.load_credentials(credentials) power = AppleTVPowerManager(hass, atv, start_off) - hass.data[DATA_APPLE_TV][host] = { - ATTR_ATV: atv, - ATTR_POWER: power - } + hass.data[DATA_APPLE_TV][host] = {ATTR_ATV: atv, ATTR_POWER: power} - hass.async_create_task(discovery.async_load_platform( - hass, 'media_player', DOMAIN, atv_config)) + hass.async_create_task( + discovery.async_load_platform(hass, "media_player", DOMAIN, atv_config) + ) - hass.async_create_task(discovery.async_load_platform( - hass, 'remote', DOMAIN, atv_config)) + hass.async_create_task( + discovery.async_load_platform(hass, "remote", DOMAIN, atv_config) + ) class AppleTVPowerManager: diff --git a/homeassistant/components/arduino.py b/homeassistant/components/arduino.py index 785f8c57f..b0ea97bab 100644 --- a/homeassistant/components/arduino.py +++ b/homeassistant/components/arduino.py @@ -8,24 +8,21 @@ import logging import voluptuous as vol -from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP from homeassistant.const import CONF_PORT import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['PyMata==2.14'] +REQUIREMENTS = ["PyMata==2.14"] _LOGGER = logging.getLogger(__name__) BOARD = None -DOMAIN = 'arduino' +DOMAIN = "arduino" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_PORT): cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({vol.Required(CONF_PORT): cv.string})}, extra=vol.ALLOW_EXTRA +) def setup(hass, config): @@ -46,8 +43,10 @@ def setup(hass, config): _LOGGER.error("The StandardFirmata sketch should be 2.2 or newer") return False except IndexError: - _LOGGER.warning("The version of the StandardFirmata sketch was not" - "detected. This may lead to side effects") + _LOGGER.warning( + "The version of the StandardFirmata sketch was not" + "detected. This may lead to side effects" + ) def stop_arduino(event): """Stop the Arduino service.""" @@ -68,26 +67,22 @@ class ArduinoBoard: def __init__(self, port): """Initialize the board.""" from PyMata.pymata import PyMata + self._port = port self._board = PyMata(self._port, verbose=False) def set_mode(self, pin, direction, mode): """Set the mode and the direction of a given pin.""" - if mode == 'analog' and direction == 'in': - self._board.set_pin_mode( - pin, self._board.INPUT, self._board.ANALOG) - elif mode == 'analog' and direction == 'out': - self._board.set_pin_mode( - pin, self._board.OUTPUT, self._board.ANALOG) - elif mode == 'digital' and direction == 'in': - self._board.set_pin_mode( - pin, self._board.INPUT, self._board.DIGITAL) - elif mode == 'digital' and direction == 'out': - self._board.set_pin_mode( - pin, self._board.OUTPUT, self._board.DIGITAL) - elif mode == 'pwm': - self._board.set_pin_mode( - pin, self._board.OUTPUT, self._board.PWM) + if mode == "analog" and direction == "in": + self._board.set_pin_mode(pin, self._board.INPUT, self._board.ANALOG) + elif mode == "analog" and direction == "out": + self._board.set_pin_mode(pin, self._board.OUTPUT, self._board.ANALOG) + elif mode == "digital" and direction == "in": + self._board.set_pin_mode(pin, self._board.INPUT, self._board.DIGITAL) + elif mode == "digital" and direction == "out": + self._board.set_pin_mode(pin, self._board.OUTPUT, self._board.DIGITAL) + elif mode == "pwm": + self._board.set_pin_mode(pin, self._board.OUTPUT, self._board.PWM) def get_analog_inputs(self): """Get the values from the pins.""" diff --git a/homeassistant/components/arlo.py b/homeassistant/components/arlo.py index 015e1e0d1..449cd9f9f 100644 --- a/homeassistant/components/arlo.py +++ b/homeassistant/components/arlo.py @@ -11,36 +11,39 @@ import voluptuous as vol from requests.exceptions import HTTPError, ConnectTimeout from homeassistant.helpers import config_validation as cv -from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL) +from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_SCAN_INTERVAL from homeassistant.helpers.event import track_time_interval from homeassistant.helpers.dispatcher import dispatcher_send -REQUIREMENTS = ['pyarlo==0.2.0'] +REQUIREMENTS = ["pyarlo==0.2.0"] _LOGGER = logging.getLogger(__name__) CONF_ATTRIBUTION = "Data provided by arlo.netgear.com" -DATA_ARLO = 'data_arlo' -DEFAULT_BRAND = 'Netgear Arlo' -DOMAIN = 'arlo' +DATA_ARLO = "data_arlo" +DEFAULT_BRAND = "Netgear Arlo" +DOMAIN = "arlo" -NOTIFICATION_ID = 'arlo_notification' -NOTIFICATION_TITLE = 'Arlo Component Setup' +NOTIFICATION_ID = "arlo_notification" +NOTIFICATION_TITLE = "Arlo Component Setup" SCAN_INTERVAL = timedelta(seconds=60) SIGNAL_UPDATE_ARLO = "arlo_update" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): - cv.time_period, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): cv.time_period, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -58,8 +61,7 @@ def setup(hass, config): return False # assign refresh period to base station thread - arlo_base_station = next(( - station for station in arlo.base_stations), None) + arlo_base_station = next((station for station in arlo.base_stations), None) if arlo_base_station is not None: arlo_base_station.refresh_rate = scan_interval.total_seconds() @@ -72,22 +74,22 @@ def setup(hass, config): except (ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Netgear Arlo: %s", str(ex)) hass.components.persistent_notification.create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) return False def hub_refresh(event_time): """Call ArloHub to refresh information.""" _LOGGER.info("Updating Arlo Hub component") - hass.data[DATA_ARLO].update(update_cameras=True, - update_base_station=True) + hass.data[DATA_ARLO].update(update_cameras=True, update_base_station=True) dispatcher_send(hass, SIGNAL_UPDATE_ARLO) # register service - hass.services.register(DOMAIN, 'update', hub_refresh) + hass.services.register(DOMAIN, "update", hub_refresh) # register scan interval for ArloHub track_time_interval(hass, hub_refresh, scan_interval) diff --git a/homeassistant/components/asterisk_mbox.py b/homeassistant/components/asterisk_mbox.py index 0d6d811db..6bcaf7a48 100644 --- a/homeassistant/components/asterisk_mbox.py +++ b/homeassistant/components/asterisk_mbox.py @@ -13,24 +13,31 @@ from homeassistant.core import callback from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, async_dispatcher_send) + async_dispatcher_connect, + async_dispatcher_send, +) -REQUIREMENTS = ['asterisk_mbox==0.5.0'] +REQUIREMENTS = ["asterisk_mbox==0.5.0"] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'asterisk_mbox' +DOMAIN = "asterisk_mbox" -SIGNAL_MESSAGE_REQUEST = 'asterisk_mbox.message_request' -SIGNAL_MESSAGE_UPDATE = 'asterisk_mbox.message_updated' +SIGNAL_MESSAGE_REQUEST = "asterisk_mbox.message_request" +SIGNAL_MESSAGE_UPDATE = "asterisk_mbox.message_updated" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_PORT): int, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_PORT): int, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -43,7 +50,7 @@ def setup(hass, config): hass.data[DOMAIN] = AsteriskData(hass, host, port, password) - discovery.load_platform(hass, 'mailbox', DOMAIN, {}, config) + discovery.load_platform(hass, "mailbox", DOMAIN, {}, config) return True @@ -60,7 +67,8 @@ class AsteriskData: self.messages = [] async_dispatcher_connect( - self.hass, SIGNAL_MESSAGE_REQUEST, self._request_messages) + self.hass, SIGNAL_MESSAGE_REQUEST, self._request_messages + ) @callback def handle_data(self, command, msg): @@ -70,9 +78,9 @@ class AsteriskData: if command == CMD_MESSAGE_LIST: _LOGGER.debug("AsteriskVM sent updated message list") self.messages = sorted( - msg, key=lambda item: item['info']['origtime'], reverse=True) - async_dispatcher_send( - self.hass, SIGNAL_MESSAGE_UPDATE, self.messages) + msg, key=lambda item: item["info"]["origtime"], reverse=True + ) + async_dispatcher_send(self.hass, SIGNAL_MESSAGE_UPDATE, self.messages) @callback def _request_messages(self): diff --git a/homeassistant/components/august.py b/homeassistant/components/august.py index 5f268a95f..0515ebdde 100644 --- a/homeassistant/components/august.py +++ b/homeassistant/components/august.py @@ -12,8 +12,7 @@ import voluptuous as vol from requests import RequestException import homeassistant.helpers.config_validation as cv -from homeassistant.const import ( - CONF_PASSWORD, CONF_USERNAME, CONF_TIMEOUT) +from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, CONF_TIMEOUT from homeassistant.helpers import discovery from homeassistant.util import Throttle @@ -21,40 +20,43 @@ _LOGGER = logging.getLogger(__name__) _CONFIGURING = {} -REQUIREMENTS = ['py-august==0.6.0'] +REQUIREMENTS = ["py-august==0.6.0"] DEFAULT_TIMEOUT = 10 ACTIVITY_FETCH_LIMIT = 10 ACTIVITY_INITIAL_FETCH_LIMIT = 20 -CONF_LOGIN_METHOD = 'login_method' -CONF_INSTALL_ID = 'install_id' +CONF_LOGIN_METHOD = "login_method" +CONF_INSTALL_ID = "install_id" -NOTIFICATION_ID = 'august_notification' +NOTIFICATION_ID = "august_notification" NOTIFICATION_TITLE = "August Setup" -AUGUST_CONFIG_FILE = '.august.conf' +AUGUST_CONFIG_FILE = ".august.conf" -DATA_AUGUST = 'august' -DOMAIN = 'august' -DEFAULT_ENTITY_NAMESPACE = 'august' +DATA_AUGUST = "august" +DOMAIN = "august" +DEFAULT_ENTITY_NAMESPACE = "august" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=5) DEFAULT_SCAN_INTERVAL = timedelta(seconds=5) -LOGIN_METHODS = ['phone', 'email'] +LOGIN_METHODS = ["phone", "email"] -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_LOGIN_METHOD): vol.In(LOGIN_METHODS), - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_INSTALL_ID): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_LOGIN_METHOD): vol.In(LOGIN_METHODS), + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_INSTALL_ID): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -AUGUST_COMPONENTS = [ - 'camera', 'binary_sensor', 'lock' -] +AUGUST_COMPONENTS = ["camera", "binary_sensor", "lock"] def request_configuration(hass, config, api, authenticator): @@ -65,12 +67,12 @@ def request_configuration(hass, config, api, authenticator): """Run when the configuration callback is called.""" from august.authenticator import ValidationResult - result = authenticator.validate_verification_code( - data.get('verification_code')) + result = authenticator.validate_verification_code(data.get("verification_code")) if result == ValidationResult.INVALID_VERIFICATION_CODE: - configurator.notify_errors(_CONFIGURING[DOMAIN], - "Invalid verification code") + configurator.notify_errors( + _CONFIGURING[DOMAIN], "Invalid verification code" + ) elif result == ValidationResult.VALIDATED: setup_august(hass, config, api, authenticator) @@ -85,12 +87,11 @@ def request_configuration(hass, config, api, authenticator): NOTIFICATION_TITLE, august_configuration_callback, description="Please check your {} ({}) and enter the verification " - "code below".format(login_method, username), - submit_caption='Verify', - fields=[{ - 'id': 'verification_code', - 'name': "Verification code", - 'type': 'string'}] + "code below".format(login_method, username), + submit_caption="Verify", + fields=[ + {"id": "verification_code", "name": "Verification code", "type": "string"} + ], ) @@ -109,7 +110,8 @@ def setup_august(hass, config, api, authenticator): "You will need to restart hass after fixing." "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) state = authentication.state @@ -146,7 +148,8 @@ def setup(hass, config): conf.get(CONF_USERNAME), conf.get(CONF_PASSWORD), install_id=conf.get(CONF_INSTALL_ID), - access_token_cache_file=hass.config.path(AUGUST_CONFIG_FILE)) + access_token_cache_file=hass.config.path(AUGUST_CONFIG_FILE), + ) return setup_august(hass, config, api, authenticator) @@ -200,14 +203,15 @@ class AugustData: def _update_device_activities(self, limit=ACTIVITY_FETCH_LIMIT): """Update data object with latest from August API.""" for house_id in self.house_ids: - activities = self._api.get_house_activities(self._access_token, - house_id, - limit=limit) + activities = self._api.get_house_activities( + self._access_token, house_id, limit=limit + ) device_ids = {a.device_id for a in activities} for device_id in device_ids: - self._activities_by_id[device_id] = [a for a in activities if - a.device_id == device_id] + self._activities_by_id[device_id] = [ + a for a in activities if a.device_id == device_id + ] def get_doorbell_detail(self, doorbell_id): """Return doorbell detail.""" @@ -220,7 +224,8 @@ class AugustData: for doorbell in self._doorbells: detail_by_id[doorbell.device_id] = self._api.get_doorbell_detail( - self._access_token, doorbell.device_id) + self._access_token, doorbell.device_id + ) self._doorbell_detail_by_id = detail_by_id @@ -241,9 +246,11 @@ class AugustData: for lock in self._locks: status_by_id[lock.device_id] = self._api.get_lock_status( - self._access_token, lock.device_id) + self._access_token, lock.device_id + ) detail_by_id[lock.device_id] = self._api.get_lock_detail( - self._access_token, lock.device_id) + self._access_token, lock.device_id + ) self._lock_status_by_id = status_by_id self._lock_detail_by_id = detail_by_id diff --git a/homeassistant/components/auth/__init__.py b/homeassistant/components/auth/__init__.py index bee72d8e4..89b51d7a9 100644 --- a/homeassistant/components/auth/__init__.py +++ b/homeassistant/components/auth/__init__.py @@ -126,8 +126,11 @@ from datetime import timedelta from aiohttp import web import voluptuous as vol -from homeassistant.auth.models import User, Credentials, \ - TOKEN_TYPE_LONG_LIVED_ACCESS_TOKEN +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 @@ -140,38 +143,39 @@ from . import indieauth from . import login_flow from . import mfa_setup_flow -DOMAIN = 'auth' -DEPENDENCIES = ['http'] +DOMAIN = "auth" +DEPENDENCIES = ["http"] -WS_TYPE_CURRENT_USER = 'auth/current_user' -SCHEMA_WS_CURRENT_USER = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_CURRENT_USER, -}) +WS_TYPE_CURRENT_USER = "auth/current_user" +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_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_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, - }) +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' +RESULT_TYPE_CREDENTIALS = "credentials" +RESULT_TYPE_USER = "user" _LOGGER = logging.getLogger(__name__) @@ -184,23 +188,20 @@ async def async_setup(hass, config): hass.http.register_view(LinkUserView(retrieve_result)) hass.components.websocket_api.async_register_command( - WS_TYPE_CURRENT_USER, websocket_current_user, - SCHEMA_WS_CURRENT_USER + 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 + SCHEMA_WS_LONG_LIVED_ACCESS_TOKEN, ) hass.components.websocket_api.async_register_command( - WS_TYPE_REFRESH_TOKENS, - websocket_refresh_tokens, - SCHEMA_WS_REFRESH_TOKENS + 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 + SCHEMA_WS_DELETE_REFRESH_TOKEN, ) await login_flow.async_setup(hass, store_result) @@ -212,8 +213,8 @@ async def async_setup(hass, config): class TokenView(HomeAssistantView): """View to issue or revoke tokens.""" - url = '/auth/token' - name = 'api:auth:token' + url = "/auth/token" + name = "api:auth:token" requires_auth = False cors_allowed = True @@ -224,29 +225,29 @@ class TokenView(HomeAssistantView): @log_invalid_auth async def post(self, request): """Grant a token.""" - hass = request.app['hass'] + hass = request.app["hass"] data = await request.post() - grant_type = data.get('grant_type') + grant_type = data.get("grant_type") # IndieAuth 6.3.5 # The revocation endpoint is the same as the token endpoint. # The revocation request includes an additional parameter, # action=revoke. - if data.get('action') == 'revoke': + if data.get("action") == "revoke": return await self._async_handle_revoke_token(hass, data) - if grant_type == 'authorization_code': + if grant_type == "authorization_code": return await self._async_handle_auth_code( - hass, data, str(request[KEY_REAL_IP])) + hass, data, str(request[KEY_REAL_IP]) + ) - if grant_type == 'refresh_token': + if grant_type == "refresh_token": return await self._async_handle_refresh_token( - hass, data, str(request[KEY_REAL_IP])) + hass, data, str(request[KEY_REAL_IP]) + ) - return self.json({ - 'error': 'unsupported_grant_type', - }, status_code=400) + return self.json({"error": "unsupported_grant_type"}, status_code=400) async def _async_handle_revoke_token(self, hass, data): """Handle revoke token request.""" @@ -254,7 +255,7 @@ class TokenView(HomeAssistantView): # 2.2 The authorization server responds with HTTP status code 200 # if the token has been revoked successfully or if the client # submitted an invalid token. - token = data.get('token') + token = data.get("token") if token is None: return web.Response(status=200) @@ -269,117 +270,112 @@ class TokenView(HomeAssistantView): async def _async_handle_auth_code(self, hass, data, remote_addr): """Handle authorization code request.""" - client_id = data.get('client_id') + client_id = data.get("client_id") if client_id is None or not indieauth.verify_client_id(client_id): - return self.json({ - 'error': 'invalid_request', - 'error_description': 'Invalid client id', - }, status_code=400) + return self.json( + {"error": "invalid_request", "error_description": "Invalid client id"}, + status_code=400, + ) - code = data.get('code') + code = data.get("code") if code is None: - return self.json({ - 'error': 'invalid_request', - 'error_description': 'Invalid code', - }, status_code=400) + return self.json( + {"error": "invalid_request", "error_description": "Invalid code"}, + status_code=400, + ) user = self._retrieve_user(client_id, RESULT_TYPE_USER, code) if user is None or not isinstance(user, User): - return self.json({ - 'error': 'invalid_request', - 'error_description': 'Invalid code', - }, status_code=400) + return self.json( + {"error": "invalid_request", "error_description": "Invalid code"}, + status_code=400, + ) # refresh user user = await hass.auth.async_get_user(user.id) if not user.is_active: - return self.json({ - 'error': 'access_denied', - 'error_description': 'User is not active', - }, status_code=403) + return self.json( + {"error": "access_denied", "error_description": "User is not active"}, + status_code=403, + ) - refresh_token = await hass.auth.async_create_refresh_token(user, - client_id) - access_token = hass.auth.async_create_access_token( - refresh_token, remote_addr) + refresh_token = await hass.auth.async_create_refresh_token(user, client_id) + access_token = hass.auth.async_create_access_token(refresh_token, remote_addr) - return self.json({ - 'access_token': access_token, - 'token_type': 'Bearer', - 'refresh_token': refresh_token.token, - 'expires_in': - int(refresh_token.access_token_expiration.total_seconds()), - }) + return self.json( + { + "access_token": access_token, + "token_type": "Bearer", + "refresh_token": refresh_token.token, + "expires_in": int( + refresh_token.access_token_expiration.total_seconds() + ), + } + ) async def _async_handle_refresh_token(self, hass, data, remote_addr): """Handle authorization code request.""" - client_id = data.get('client_id') + client_id = data.get("client_id") if client_id is not None and not indieauth.verify_client_id(client_id): - return self.json({ - 'error': 'invalid_request', - 'error_description': 'Invalid client id', - }, status_code=400) + return self.json( + {"error": "invalid_request", "error_description": "Invalid client id"}, + status_code=400, + ) - token = data.get('refresh_token') + token = data.get("refresh_token") if token is None: - return self.json({ - 'error': 'invalid_request', - }, status_code=400) + return self.json({"error": "invalid_request"}, status_code=400) refresh_token = await hass.auth.async_get_refresh_token_by_token(token) if refresh_token is None: - return self.json({ - 'error': 'invalid_grant', - }, status_code=400) + return self.json({"error": "invalid_grant"}, status_code=400) if refresh_token.client_id != client_id: - return self.json({ - 'error': 'invalid_request', - }, status_code=400) + return self.json({"error": "invalid_request"}, status_code=400) - access_token = hass.auth.async_create_access_token( - refresh_token, remote_addr) + access_token = hass.auth.async_create_access_token(refresh_token, remote_addr) - return self.json({ - 'access_token': access_token, - 'token_type': 'Bearer', - 'expires_in': - int(refresh_token.access_token_expiration.total_seconds()), - }) + return self.json( + { + "access_token": access_token, + "token_type": "Bearer", + "expires_in": int( + refresh_token.access_token_expiration.total_seconds() + ), + } + ) class LinkUserView(HomeAssistantView): """View to link existing users to new credentials.""" - url = '/auth/link_user' - name = 'api:auth:link_user' + url = "/auth/link_user" + name = "api:auth:link_user" def __init__(self, retrieve_credentials): """Initialize the link user view.""" self._retrieve_credentials = retrieve_credentials - @RequestDataValidator(vol.Schema({ - 'code': str, - 'client_id': str, - })) + @RequestDataValidator(vol.Schema({"code": str, "client_id": str})) async def post(self, request, data): """Link a user.""" - hass = request.app['hass'] - user = request['hass_user'] + hass = request.app["hass"] + user = request["hass_user"] credentials = self._retrieve_credentials( - data['client_id'], RESULT_TYPE_CREDENTIALS, data['code']) + data["client_id"], RESULT_TYPE_CREDENTIALS, data["code"] + ) if credentials is None: - return self.json_message('Invalid code', status_code=400) + return self.json_message("Invalid code", status_code=400) await hass.auth.async_link_user(user, credentials) - return self.json_message('User linked') + return self.json_message("User linked") @callback @@ -395,11 +391,14 @@ def _create_auth_code_store(): elif isinstance(result, Credentials): result_type = RESULT_TYPE_CREDENTIALS else: - raise ValueError('result has to be either User or Credentials') + raise ValueError("result has to be either User or Credentials") code = uuid.uuid4().hex - temp_results[(client_id, result_type, code)] = \ - (dt_util.utcnow(), result_type, result) + temp_results[(client_id, result_type, code)] = ( + dt_util.utcnow(), + result_type, + result, + ) return code @callback @@ -427,26 +426,39 @@ def _create_auth_code_store(): @websocket_api.ws_require_user() @callback def websocket_current_user( - hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg +): """Return the current user.""" + async def async_get_current_user(user): """Get current user.""" enabled_modules = await hass.auth.async_get_enabled_mfa(user) connection.send_message_outside( - websocket_api.result_message(msg['id'], { - 'id': user.id, - 'name': user.name, - 'is_owner': user.is_owner, - 'credentials': [{'auth_provider_type': c.auth_provider_type, - 'auth_provider_id': c.auth_provider_id} - for c in user.credentials], - 'mfa_modules': [{ - 'id': module.id, - 'name': module.name, - 'enabled': module.id in enabled_modules, - } for module in hass.auth.auth_mfa_modules], - })) + websocket_api.result_message( + msg["id"], + { + "id": user.id, + "name": user.name, + "is_owner": user.is_owner, + "credentials": [ + { + "auth_provider_type": c.auth_provider_type, + "auth_provider_id": c.auth_provider_id, + } + for c in user.credentials + ], + "mfa_modules": [ + { + "id": module.id, + "name": module.name, + "enabled": module.id in enabled_modules, + } + for module in hass.auth.auth_mfa_modules + ], + }, + ) + ) hass.async_create_task(async_get_current_user(connection.user)) @@ -454,63 +466,77 @@ def websocket_current_user( @websocket_api.ws_require_user() @callback def websocket_create_long_lived_access_token( - hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): + 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'), + 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_expiration=timedelta(days=msg["lifespan"]), + ) - access_token = hass.auth.async_create_access_token( - refresh_token) + access_token = hass.auth.async_create_access_token(refresh_token) connection.send_message_outside( - websocket_api.result_message(msg['id'], access_token)) + websocket_api.result_message(msg["id"], access_token) + ) - hass.async_create_task( - async_create_long_lived_access_token(connection.user)) + 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): + 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()])) + 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): + 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') + 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'], {})) + connection.send_message_outside(websocket_api.result_message(msg["id"], {})) hass.async_create_task( - async_delete_refresh_token(connection.user, msg['refresh_token_id'])) + async_delete_refresh_token(connection.user, msg["refresh_token_id"]) + ) diff --git a/homeassistant/components/auth/indieauth.py b/homeassistant/components/auth/indieauth.py index bcf73258f..93f907e27 100644 --- a/homeassistant/components/auth/indieauth.py +++ b/homeassistant/components/auth/indieauth.py @@ -8,16 +8,13 @@ import aiohttp from aiohttp.client_exceptions import ClientError # IP addresses of loopback interfaces -ALLOWED_IPS = ( - ip_address('127.0.0.1'), - ip_address('::1'), -) +ALLOWED_IPS = (ip_address("127.0.0.1"), ip_address("::1")) # RFC1918 - Address allocation for Private Internets ALLOWED_NETWORKS = ( - ip_network('10.0.0.0/8'), - ip_network('172.16.0.0/12'), - ip_network('192.168.0.0/16'), + ip_network("10.0.0.0/8"), + ip_network("172.16.0.0/12"), + ip_network("192.168.0.0/16"), ) @@ -32,8 +29,8 @@ async def verify_redirect_uri(hass, client_id, redirect_uri): # Verify redirect url and client url have same scheme and domain. is_valid = ( - client_id_parts.scheme == redirect_parts.scheme and - client_id_parts.netloc == redirect_parts.netloc + client_id_parts.scheme == redirect_parts.scheme + and client_id_parts.netloc == redirect_parts.netloc ) if is_valid: @@ -56,13 +53,13 @@ class LinkTagParser(HTMLParser): def handle_starttag(self, tag, attrs): """Handle finding a start tag.""" - if tag != 'link': + if tag != "link": return attrs = dict(attrs) - if attrs.get('rel') == self.rel: - self.found.append(attrs.get('href')) + if attrs.get("rel") == self.rel: + self.found.append(attrs.get("href")) async def fetch_redirect_uris(hass, url): @@ -77,7 +74,7 @@ async def fetch_redirect_uris(hass, url): We do not implement extracting redirect uris from headers. """ - parser = LinkTagParser('redirect_uri') + parser = LinkTagParser("redirect_uri") chunks = 0 try: async with aiohttp.ClientSession() as session: @@ -119,8 +116,8 @@ def _parse_url(url): # If a URL with no path component is ever encountered, # it MUST be treated as if it had the path /. - if parts.path == '': - parts = parts._replace(path='/') + if parts.path == "": + parts = parts._replace(path="/") return parts @@ -134,34 +131,35 @@ def _parse_client_id(client_id): # Client identifier URLs # MUST have either an https or http scheme - if parts.scheme not in ('http', 'https'): + if parts.scheme not in ("http", "https"): raise ValueError() # MUST contain a path component # Handled by url canonicalization. # MUST NOT contain single-dot or double-dot path segments - if any(segment in ('.', '..') for segment in parts.path.split('/')): + if any(segment in (".", "..") for segment in parts.path.split("/")): raise ValueError( - 'Client ID cannot contain single-dot or double-dot path segments') + "Client ID cannot contain single-dot or double-dot path segments" + ) # MUST NOT contain a fragment component - if parts.fragment != '': - raise ValueError('Client ID cannot contain a fragment') + if parts.fragment != "": + raise ValueError("Client ID cannot contain a fragment") # MUST NOT contain a username or password component if parts.username is not None: - raise ValueError('Client ID cannot contain username') + raise ValueError("Client ID cannot contain username") if parts.password is not None: - raise ValueError('Client ID cannot contain password') + raise ValueError("Client ID cannot contain password") # MAY contain a port try: # parts raises ValueError when port cannot be parsed as int parts.port except ValueError: - raise ValueError('Client ID contains invalid port') + raise ValueError("Client ID contains invalid port") # Additionally, hostnames # MUST be domain names or a loopback interface and @@ -177,7 +175,7 @@ def _parse_client_id(client_id): netloc = parts.netloc # Strip the [, ] from ipv6 addresses before parsing - if netloc[0] == '[' and netloc[-1] == ']': + if netloc[0] == "[" and netloc[-1] == "]": netloc = netloc[1:-1] address = ip_address(netloc) @@ -185,9 +183,11 @@ def _parse_client_id(client_id): # Not an ip address pass - if (address is None or - address in ALLOWED_IPS or - any(address in network for network in ALLOWED_NETWORKS)): + if ( + address is None + or address in ALLOWED_IPS + or any(address in network for network in ALLOWED_NETWORKS) + ): return parts - raise ValueError('Hostname should be a domain name or local IP address') + raise ValueError("Hostname should be a domain name or local IP address") diff --git a/homeassistant/components/auth/login_flow.py b/homeassistant/components/auth/login_flow.py index 73a739c29..d7cffd2da 100644 --- a/homeassistant/components/auth/login_flow.py +++ b/homeassistant/components/auth/login_flow.py @@ -71,8 +71,7 @@ import voluptuous as vol from homeassistant import data_entry_flow from homeassistant.components.http import KEY_REAL_IP -from homeassistant.components.http.ban import process_wrong_login, \ - log_invalid_auth +from homeassistant.components.http.ban import process_wrong_login, log_invalid_auth from homeassistant.components.http.data_validator import RequestDataValidator from homeassistant.components.http.view import HomeAssistantView from . import indieauth @@ -82,55 +81,55 @@ async def async_setup(hass, store_result): """Component to allow users to login.""" hass.http.register_view(AuthProvidersView) hass.http.register_view(LoginFlowIndexView(hass.auth.login_flow)) - hass.http.register_view( - LoginFlowResourceView(hass.auth.login_flow, store_result)) + hass.http.register_view(LoginFlowResourceView(hass.auth.login_flow, store_result)) class AuthProvidersView(HomeAssistantView): """View to get available auth providers.""" - url = '/auth/providers' - name = 'api:auth:providers' + url = "/auth/providers" + name = "api:auth:providers" requires_auth = False async def get(self, request): """Get available auth providers.""" - hass = request.app['hass'] + hass = request.app["hass"] if not hass.components.onboarding.async_is_onboarded(): return self.json_message( - message='Onboarding not finished', + message="Onboarding not finished", status_code=400, - message_code='onboarding_required' + message_code="onboarding_required", ) - return self.json([{ - 'name': provider.name, - 'id': provider.id, - 'type': provider.type, - } for provider in hass.auth.auth_providers]) + return self.json( + [ + {"name": provider.name, "id": provider.id, "type": provider.type} + for provider in hass.auth.auth_providers + ] + ) def _prepare_result_json(result): """Convert result to JSON.""" - if result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: data = result.copy() - data.pop('result') - data.pop('data') + data.pop("result") + data.pop("data") return data - if result['type'] != data_entry_flow.RESULT_TYPE_FORM: + if result["type"] != data_entry_flow.RESULT_TYPE_FORM: return result import voluptuous_serialize data = result.copy() - schema = data['data_schema'] + schema = data["data_schema"] if schema is None: - data['data_schema'] = [] + data["data_schema"] = [] else: - data['data_schema'] = voluptuous_serialize.convert(schema) + data["data_schema"] = voluptuous_serialize.convert(schema) return data @@ -138,8 +137,8 @@ def _prepare_result_json(result): class LoginFlowIndexView(HomeAssistantView): """View to create a config flow.""" - url = '/auth/login_flow' - name = 'api:auth:login_flow' + url = "/auth/login_flow" + name = "api:auth:login_flow" requires_auth = False def __init__(self, flow_mgr): @@ -150,34 +149,41 @@ class LoginFlowIndexView(HomeAssistantView): """Do not allow index of flows in progress.""" return web.Response(status=405) - @RequestDataValidator(vol.Schema({ - vol.Required('client_id'): str, - vol.Required('handler'): vol.Any(str, list), - vol.Required('redirect_uri'): str, - vol.Optional('type', default='authorize'): str, - })) + @RequestDataValidator( + vol.Schema( + { + vol.Required("client_id"): str, + vol.Required("handler"): vol.Any(str, list), + vol.Required("redirect_uri"): str, + vol.Optional("type", default="authorize"): str, + } + ) + ) @log_invalid_auth async def post(self, request, data): """Create a new login flow.""" if not await indieauth.verify_redirect_uri( - request.app['hass'], data['client_id'], data['redirect_uri']): - return self.json_message('invalid client id or redirect uri', 400) + request.app["hass"], data["client_id"], data["redirect_uri"] + ): + return self.json_message("invalid client id or redirect uri", 400) - if isinstance(data['handler'], list): - handler = tuple(data['handler']) + if isinstance(data["handler"], list): + handler = tuple(data["handler"]) else: - handler = data['handler'] + handler = data["handler"] try: result = await self._flow_mgr.async_init( - handler, context={ - 'ip_address': request[KEY_REAL_IP], - 'credential_only': data.get('type') == 'link_user', - }) + handler, + context={ + "ip_address": request[KEY_REAL_IP], + "credential_only": data.get("type") == "link_user", + }, + ) except data_entry_flow.UnknownHandler: - return self.json_message('Invalid handler specified', 404) + return self.json_message("Invalid handler specified", 404) except data_entry_flow.UnknownStep: - return self.json_message('Handler does not support init', 400) + return self.json_message("Handler does not support init", 400) return self.json(_prepare_result_json(result)) @@ -185,8 +191,8 @@ class LoginFlowIndexView(HomeAssistantView): class LoginFlowResourceView(HomeAssistantView): """View to interact with the flow manager.""" - url = '/auth/login_flow/{flow_id}' - name = 'api:auth:login_flow:resource' + url = "/auth/login_flow/{flow_id}" + name = "api:auth:login_flow:resource" requires_auth = False def __init__(self, flow_mgr, store_result): @@ -196,43 +202,43 @@ class LoginFlowResourceView(HomeAssistantView): async def get(self, request): """Do not allow getting status of a flow in progress.""" - return self.json_message('Invalid flow specified', 404) + return self.json_message("Invalid flow specified", 404) - @RequestDataValidator(vol.Schema({ - 'client_id': str - }, extra=vol.ALLOW_EXTRA)) + @RequestDataValidator(vol.Schema({"client_id": str}, extra=vol.ALLOW_EXTRA)) @log_invalid_auth async def post(self, request, flow_id, data): """Handle progressing a login flow request.""" - client_id = data.pop('client_id') + client_id = data.pop("client_id") if not indieauth.verify_client_id(client_id): - return self.json_message('Invalid client id', 400) + return self.json_message("Invalid client id", 400) try: # do not allow change ip during login flow for flow in self._flow_mgr.async_progress(): - if (flow['flow_id'] == flow_id and - flow['context']['ip_address'] != - request.get(KEY_REAL_IP)): - return self.json_message('IP address changed', 400) + if flow["flow_id"] == flow_id and flow["context"][ + "ip_address" + ] != request.get(KEY_REAL_IP): + return self.json_message("IP address changed", 400) result = await self._flow_mgr.async_configure(flow_id, data) except data_entry_flow.UnknownFlow: - return self.json_message('Invalid flow specified', 404) + return self.json_message("Invalid flow specified", 404) except vol.Invalid: - return self.json_message('User input malformed', 400) + return self.json_message("User input malformed", 400) - if result['type'] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + if result["type"] != data_entry_flow.RESULT_TYPE_CREATE_ENTRY: # @log_invalid_auth does not work here since it returns HTTP 200 # need manually log failed login attempts - if result['errors'] is not None and \ - result['errors'].get('base') == 'invalid_auth': + if ( + result["errors"] is not None + and result["errors"].get("base") == "invalid_auth" + ): await process_wrong_login(request) return self.json(_prepare_result_json(result)) - result.pop('data') - result['result'] = self._store_result(client_id, result['result']) + result.pop("data") + result["result"] = self._store_result(client_id, result["result"]) return self.json(result) @@ -241,6 +247,6 @@ class LoginFlowResourceView(HomeAssistantView): try: self._flow_mgr.async_abort(flow_id) except data_entry_flow.UnknownFlow: - return self.json_message('Invalid flow specified', 404) + return self.json_message("Invalid flow specified", 404) - return self.json_message('Flow aborted') + return self.json_message("Flow aborted") diff --git a/homeassistant/components/auth/mfa_setup_flow.py b/homeassistant/components/auth/mfa_setup_flow.py index 82eb913d8..fe296425b 100644 --- a/homeassistant/components/auth/mfa_setup_flow.py +++ b/homeassistant/components/auth/mfa_setup_flow.py @@ -7,82 +7,93 @@ from homeassistant import data_entry_flow from homeassistant.components import websocket_api from homeassistant.core import callback, HomeAssistant -WS_TYPE_SETUP_MFA = 'auth/setup_mfa' -SCHEMA_WS_SETUP_MFA = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_SETUP_MFA, - vol.Exclusive('mfa_module_id', 'module_or_flow_id'): str, - vol.Exclusive('flow_id', 'module_or_flow_id'): str, - vol.Optional('user_input'): object, -}) +WS_TYPE_SETUP_MFA = "auth/setup_mfa" +SCHEMA_WS_SETUP_MFA = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_SETUP_MFA, + vol.Exclusive("mfa_module_id", "module_or_flow_id"): str, + vol.Exclusive("flow_id", "module_or_flow_id"): str, + vol.Optional("user_input"): object, + } +) -WS_TYPE_DEPOSE_MFA = 'auth/depose_mfa' -SCHEMA_WS_DEPOSE_MFA = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_DEPOSE_MFA, - vol.Required('mfa_module_id'): str, -}) +WS_TYPE_DEPOSE_MFA = "auth/depose_mfa" +SCHEMA_WS_DEPOSE_MFA = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_DEPOSE_MFA, vol.Required("mfa_module_id"): str} +) -DATA_SETUP_FLOW_MGR = 'auth_mfa_setup_flow_manager' +DATA_SETUP_FLOW_MGR = "auth_mfa_setup_flow_manager" _LOGGER = logging.getLogger(__name__) async def async_setup(hass): """Init mfa setup flow manager.""" + async def _async_create_setup_flow(handler, context, data): """Create a setup flow. hanlder is a mfa module.""" mfa_module = hass.auth.get_auth_mfa_module(handler) if mfa_module is None: - raise ValueError('Mfa module {} is not found'.format(handler)) + raise ValueError("Mfa module {} is not found".format(handler)) - user_id = data.pop('user_id') + user_id = data.pop("user_id") return await mfa_module.async_setup_flow(user_id) async def _async_finish_setup_flow(flow, flow_result): - _LOGGER.debug('flow_result: %s', flow_result) + _LOGGER.debug("flow_result: %s", flow_result) return flow_result hass.data[DATA_SETUP_FLOW_MGR] = data_entry_flow.FlowManager( - hass, _async_create_setup_flow, _async_finish_setup_flow) + hass, _async_create_setup_flow, _async_finish_setup_flow + ) hass.components.websocket_api.async_register_command( - WS_TYPE_SETUP_MFA, websocket_setup_mfa, SCHEMA_WS_SETUP_MFA) + WS_TYPE_SETUP_MFA, websocket_setup_mfa, SCHEMA_WS_SETUP_MFA + ) hass.components.websocket_api.async_register_command( - WS_TYPE_DEPOSE_MFA, websocket_depose_mfa, SCHEMA_WS_DEPOSE_MFA) + WS_TYPE_DEPOSE_MFA, websocket_depose_mfa, SCHEMA_WS_DEPOSE_MFA + ) @callback @websocket_api.ws_require_user(allow_system_user=False) def websocket_setup_mfa( - hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg +): """Return a setup flow for mfa auth module.""" + async def async_setup_flow(msg): """Return a setup flow for mfa auth module.""" flow_manager = hass.data[DATA_SETUP_FLOW_MGR] - flow_id = msg.get('flow_id') + flow_id = msg.get("flow_id") if flow_id is not None: - result = await flow_manager.async_configure( - flow_id, msg.get('user_input')) + result = await flow_manager.async_configure(flow_id, msg.get("user_input")) connection.send_message_outside( - websocket_api.result_message( - msg['id'], _prepare_result_json(result))) + websocket_api.result_message(msg["id"], _prepare_result_json(result)) + ) return - mfa_module_id = msg.get('mfa_module_id') + mfa_module_id = msg.get("mfa_module_id") mfa_module = hass.auth.get_auth_mfa_module(mfa_module_id) if mfa_module is None: - connection.send_message_outside(websocket_api.error_message( - msg['id'], 'no_module', - 'MFA module {} is not found'.format(mfa_module_id))) + connection.send_message_outside( + websocket_api.error_message( + msg["id"], + "no_module", + "MFA module {} is not found".format(mfa_module_id), + ) + ) return result = await flow_manager.async_init( - mfa_module_id, data={'user_id': connection.user.id}) + mfa_module_id, data={"user_id": connection.user.id} + ) connection.send_message_outside( - websocket_api.result_message( - msg['id'], _prepare_result_json(result))) + websocket_api.result_message(msg["id"], _prepare_result_json(result)) + ) hass.async_create_task(async_setup_flow(msg)) @@ -90,45 +101,49 @@ def websocket_setup_mfa( @callback @websocket_api.ws_require_user(allow_system_user=False) def websocket_depose_mfa( - hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg): + hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg +): """Remove user from mfa module.""" + async def async_depose(msg): """Remove user from mfa auth module.""" - mfa_module_id = msg['mfa_module_id'] + mfa_module_id = msg["mfa_module_id"] try: await hass.auth.async_disable_user_mfa( - connection.user, msg['mfa_module_id']) + connection.user, msg["mfa_module_id"] + ) except ValueError as err: - connection.send_message_outside(websocket_api.error_message( - msg['id'], 'disable_failed', - 'Cannot disable MFA Module {}: {}'.format( - mfa_module_id, err))) + connection.send_message_outside( + websocket_api.error_message( + msg["id"], + "disable_failed", + "Cannot disable MFA Module {}: {}".format(mfa_module_id, err), + ) + ) return - connection.send_message_outside( - websocket_api.result_message( - msg['id'], 'done')) + connection.send_message_outside(websocket_api.result_message(msg["id"], "done")) hass.async_create_task(async_depose(msg)) def _prepare_result_json(result): """Convert result to JSON.""" - if result['type'] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: + if result["type"] == data_entry_flow.RESULT_TYPE_CREATE_ENTRY: data = result.copy() return data - if result['type'] != data_entry_flow.RESULT_TYPE_FORM: + if result["type"] != data_entry_flow.RESULT_TYPE_FORM: return result import voluptuous_serialize data = result.copy() - schema = data['data_schema'] + schema = data["data_schema"] if schema is None: - data['data_schema'] = [] + data["data_schema"] = [] else: - data['data_schema'] = voluptuous_serialize.convert(schema) + data["data_schema"] = voluptuous_serialize.convert(schema) return data diff --git a/homeassistant/components/automation/__init__.py b/homeassistant/components/automation/__init__.py index 43fd4cedb..31cf3ade3 100644 --- a/homeassistant/components/automation/__init__.py +++ b/homeassistant/components/automation/__init__.py @@ -15,8 +15,16 @@ from homeassistant.setup import async_prepare_setup_platform from homeassistant.core import CoreState from homeassistant.loader import bind_hass from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_PLATFORM, STATE_ON, SERVICE_TURN_ON, SERVICE_TURN_OFF, - SERVICE_TOGGLE, SERVICE_RELOAD, EVENT_HOMEASSISTANT_START, CONF_ID) + ATTR_ENTITY_ID, + CONF_PLATFORM, + STATE_ON, + SERVICE_TURN_ON, + SERVICE_TURN_OFF, + SERVICE_TOGGLE, + SERVICE_RELOAD, + EVENT_HOMEASSISTANT_START, + CONF_ID, +) from homeassistant.components import logbook from homeassistant.exceptions import HomeAssistantError from homeassistant.helpers import extract_domain_configs, script, condition @@ -26,32 +34,32 @@ from homeassistant.helpers.restore_state import async_get_last_state from homeassistant.util.dt import utcnow import homeassistant.helpers.config_validation as cv -DOMAIN = 'automation' -DEPENDENCIES = ['group'] -ENTITY_ID_FORMAT = DOMAIN + '.{}' +DOMAIN = "automation" +DEPENDENCIES = ["group"] +ENTITY_ID_FORMAT = DOMAIN + ".{}" -GROUP_NAME_ALL_AUTOMATIONS = 'all automations' +GROUP_NAME_ALL_AUTOMATIONS = "all automations" -CONF_ALIAS = 'alias' -CONF_HIDE_ENTITY = 'hide_entity' +CONF_ALIAS = "alias" +CONF_HIDE_ENTITY = "hide_entity" -CONF_CONDITION = 'condition' -CONF_ACTION = 'action' -CONF_TRIGGER = 'trigger' -CONF_CONDITION_TYPE = 'condition_type' -CONF_INITIAL_STATE = 'initial_state' +CONF_CONDITION = "condition" +CONF_ACTION = "action" +CONF_TRIGGER = "trigger" +CONF_CONDITION_TYPE = "condition_type" +CONF_INITIAL_STATE = "initial_state" -CONDITION_USE_TRIGGER_VALUES = 'use_trigger_values' -CONDITION_TYPE_AND = 'and' -CONDITION_TYPE_OR = 'or' +CONDITION_USE_TRIGGER_VALUES = "use_trigger_values" +CONDITION_TYPE_AND = "and" +CONDITION_TYPE_OR = "or" DEFAULT_CONDITION_TYPE = CONDITION_TYPE_AND DEFAULT_HIDE_ENTITY = False DEFAULT_INITIAL_STATE = True -ATTR_LAST_TRIGGERED = 'last_triggered' -ATTR_VARIABLES = 'variables' -SERVICE_TRIGGER = 'trigger' +ATTR_LAST_TRIGGERED = "last_triggered" +ATTR_VARIABLES = "variables" +SERVICE_TRIGGER = "trigger" _LOGGER = logging.getLogger(__name__) @@ -60,10 +68,10 @@ def _platform_validator(config): """Validate it is a valid platform.""" try: platform = importlib.import_module( - 'homeassistant.components.automation.{}'.format( - config[CONF_PLATFORM])) + "homeassistant.components.automation.{}".format(config[CONF_PLATFORM]) + ) except ImportError: - raise vol.Invalid('Invalid platform specified') from None + raise vol.Invalid("Invalid platform specified") from None return platform.TRIGGER_SCHEMA(config) @@ -72,35 +80,35 @@ _TRIGGER_SCHEMA = vol.All( cv.ensure_list, [ vol.All( - vol.Schema({ - vol.Required(CONF_PLATFORM): str - }, extra=vol.ALLOW_EXTRA), - _platform_validator - ), - ] + vol.Schema({vol.Required(CONF_PLATFORM): str}, extra=vol.ALLOW_EXTRA), + _platform_validator, + ) + ], ) _CONDITION_SCHEMA = vol.All(cv.ensure_list, [cv.CONDITION_SCHEMA]) -PLATFORM_SCHEMA = vol.Schema({ - # str on purpose - CONF_ID: str, - CONF_ALIAS: cv.string, - vol.Optional(CONF_INITIAL_STATE): cv.boolean, - vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean, - vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA, - vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA, - vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA, -}) +PLATFORM_SCHEMA = vol.Schema( + { + # str on purpose + CONF_ID: str, + CONF_ALIAS: cv.string, + vol.Optional(CONF_INITIAL_STATE): cv.boolean, + vol.Optional(CONF_HIDE_ENTITY, default=DEFAULT_HIDE_ENTITY): cv.boolean, + vol.Required(CONF_TRIGGER): _TRIGGER_SCHEMA, + vol.Optional(CONF_CONDITION): _CONDITION_SCHEMA, + vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA, + } +) -SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, -}) +SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) -TRIGGER_SERVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_ENTITY_ID): cv.entity_ids, - vol.Optional(ATTR_VARIABLES, default={}): dict, -}) +TRIGGER_SERVICE_SCHEMA = vol.Schema( + { + vol.Required(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_VARIABLES, default={}): dict, + } +) RELOAD_SERVICE_SCHEMA = vol.Schema({}) @@ -160,8 +168,9 @@ def async_reload(hass): async def async_setup(hass, config): """Set up the automation.""" - component = EntityComponent(_LOGGER, DOMAIN, hass, - group_name=GROUP_NAME_ALL_AUTOMATIONS) + component = EntityComponent( + _LOGGER, DOMAIN, hass, group_name=GROUP_NAME_ALL_AUTOMATIONS + ) await _async_process_config(hass, config, component) @@ -169,10 +178,13 @@ async def async_setup(hass, config): """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), - skip_condition=True, - context=service_call.context)) + tasks.append( + entity.async_trigger( + service_call.data.get(ATTR_VARIABLES), + skip_condition=True, + context=service_call.context, + ) + ) if tasks: await asyncio.wait(tasks, loop=hass.loop) @@ -180,7 +192,7 @@ async def async_setup(hass, config): async def turn_onoff_service_handler(service_call): """Handle automation turn on/off service calls.""" tasks = [] - method = 'async_{}'.format(service_call.service) + method = "async_{}".format(service_call.service) for entity in component.async_extract_from_service(service_call): tasks.append(getattr(entity, method)()) @@ -207,21 +219,21 @@ async def async_setup(hass, config): await _async_process_config(hass, conf, component) hass.services.async_register( - DOMAIN, SERVICE_TRIGGER, trigger_service_handler, - schema=TRIGGER_SERVICE_SCHEMA) + DOMAIN, SERVICE_TRIGGER, trigger_service_handler, schema=TRIGGER_SERVICE_SCHEMA + ) hass.services.async_register( - DOMAIN, SERVICE_RELOAD, reload_service_handler, - schema=RELOAD_SERVICE_SCHEMA) + DOMAIN, SERVICE_RELOAD, reload_service_handler, schema=RELOAD_SERVICE_SCHEMA + ) hass.services.async_register( - DOMAIN, SERVICE_TOGGLE, toggle_service_handler, - schema=SERVICE_SCHEMA) + DOMAIN, SERVICE_TOGGLE, toggle_service_handler, schema=SERVICE_SCHEMA + ) for service in (SERVICE_TURN_ON, SERVICE_TURN_OFF): hass.services.async_register( - DOMAIN, service, turn_onoff_service_handler, - schema=SERVICE_SCHEMA) + DOMAIN, service, turn_onoff_service_handler, schema=SERVICE_SCHEMA + ) return True @@ -229,8 +241,16 @@ async def async_setup(hass, config): class AutomationEntity(ToggleEntity): """Entity to show status of entity.""" - def __init__(self, automation_id, name, async_attach_triggers, cond_func, - async_action, hidden, initial_state): + def __init__( + self, + automation_id, + name, + async_attach_triggers, + cond_func, + async_action, + hidden, + initial_state, + ): """Initialize an automation entity.""" self._id = automation_id self._name = name @@ -255,9 +275,7 @@ class AutomationEntity(ToggleEntity): @property def state_attributes(self): """Return the entity state attributes.""" - return { - ATTR_LAST_TRIGGERED: self._last_triggered - } + return {ATTR_LAST_TRIGGERED: self._last_triggered} @property def hidden(self) -> bool: @@ -273,33 +291,43 @@ class AutomationEntity(ToggleEntity): """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) + _LOGGER.debug( + "Automation %s initial state %s from config " "initial_state", + self.entity_id, + enable_automation, + ) else: 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') - _LOGGER.debug("Automation %s initial state %s from recorder " - "last state %s", self.entity_id, - enable_automation, state) + self._last_triggered = state.attributes.get("last_triggered") + _LOGGER.debug( + "Automation %s initial state %s from recorder " "last state %s", + self.entity_id, + enable_automation, + state, + ) else: enable_automation = DEFAULT_INITIAL_STATE - _LOGGER.debug("Automation %s initial state %s from default " - "initial state", self.entity_id, - enable_automation) + _LOGGER.debug( + "Automation %s initial state %s from default " "initial state", + self.entity_id, + enable_automation, + ) if not enable_automation: return # HomeAssistant is starting up if self.hass.state == CoreState.not_running: + async def async_enable_automation(event): """Start automation on startup.""" await self.async_enable() self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, async_enable_automation) + EVENT_HOMEASSISTANT_START, async_enable_automation + ) # HomeAssistant is running else: @@ -321,8 +349,7 @@ class AutomationEntity(ToggleEntity): self._async_detach_triggers = None await self.async_update_ha_state() - async def async_trigger(self, variables, skip_condition=False, - context=None): + async def async_trigger(self, variables, skip_condition=False, context=None): """Trigger automation. This method is a coroutine. @@ -346,7 +373,8 @@ class AutomationEntity(ToggleEntity): return self._async_detach_triggers = await self._async_attach_triggers( - self.async_trigger) + self.async_trigger + ) await self.async_update_ha_state() @property @@ -355,9 +383,7 @@ class AutomationEntity(ToggleEntity): if self._id is None: return None - return { - CONF_ID: self._id - } + return {CONF_ID: self._id} async def _async_process_config(hass, config, component): @@ -372,14 +398,12 @@ async def _async_process_config(hass, config, component): for list_no, config_block in enumerate(conf): automation_id = config_block.get(CONF_ID) - name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key, - list_no) + name = config_block.get(CONF_ALIAS) or "{} {}".format(config_key, list_no) hidden = config_block[CONF_HIDE_ENTITY] initial_state = config_block.get(CONF_INITIAL_STATE) - action = _async_get_action(hass, config_block.get(CONF_ACTION, {}), - name) + action = _async_get_action(hass, config_block.get(CONF_ACTION, {}), name) if CONF_CONDITION in config_block: cond_func = _async_process_if(hass, config, config_block) @@ -387,17 +411,27 @@ async def _async_process_config(hass, config, component): if cond_func is None: continue else: + def cond_func(variables): """Condition will always pass.""" return True async_attach_triggers = partial( - _async_process_trigger, hass, config, - config_block.get(CONF_TRIGGER, []), name + _async_process_trigger, + hass, + config, + config_block.get(CONF_TRIGGER, []), + name, ) entity = AutomationEntity( - automation_id, name, async_attach_triggers, cond_func, action, - hidden, initial_state) + automation_id, + name, + async_attach_triggers, + cond_func, + action, + hidden, + initial_state, + ) entities.append(entity) @@ -411,9 +445,8 @@ def _async_get_action(hass, config, name): 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) + _LOGGER.info("Executing %s", name) + logbook.async_log_entry(hass, name, "has been triggered", DOMAIN, entity_id) await script_obj.async_run(variables, context) return action @@ -428,7 +461,7 @@ def _async_process_if(hass, config, p_config): try: checks.append(condition.async_from_config(if_config, False)) except HomeAssistantError as ex: - _LOGGER.warning('Invalid condition: %s', ex) + _LOGGER.warning("Invalid condition: %s", ex) return None def if_action(variables=None): @@ -447,7 +480,8 @@ async def _async_process_trigger(hass, config, trigger_configs, name, action): for conf in trigger_configs: platform = await async_prepare_setup_platform( - hass, config, DOMAIN, conf.get(CONF_PLATFORM)) + hass, config, DOMAIN, conf.get(CONF_PLATFORM) + ) if platform is None: return None diff --git a/homeassistant/components/automation/event.py b/homeassistant/components/automation/event.py index e19a85eda..0a3781c21 100644 --- a/homeassistant/components/automation/event.py +++ b/homeassistant/components/automation/event.py @@ -13,25 +13,29 @@ from homeassistant.core import callback from homeassistant.const import CONF_PLATFORM from homeassistant.helpers import config_validation as cv -CONF_EVENT_TYPE = 'event_type' -CONF_EVENT_DATA = 'event_data' +CONF_EVENT_TYPE = "event_type" +CONF_EVENT_DATA = "event_data" _LOGGER = logging.getLogger(__name__) -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'event', - vol.Required(CONF_EVENT_TYPE): cv.string, - vol.Optional(CONF_EVENT_DATA): dict, -}) +TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "event", + vol.Required(CONF_EVENT_TYPE): cv.string, + vol.Optional(CONF_EVENT_DATA): dict, + } +) @asyncio.coroutine def async_trigger(hass, config, action): """Listen for events based on configuration.""" event_type = config.get(CONF_EVENT_TYPE) - event_data_schema = vol.Schema( - config.get(CONF_EVENT_DATA), - extra=vol.ALLOW_EXTRA) if config.get(CONF_EVENT_DATA) else None + event_data_schema = ( + vol.Schema(config.get(CONF_EVENT_DATA), extra=vol.ALLOW_EXTRA) + if config.get(CONF_EVENT_DATA) + else None + ) @callback def handle_event(event): @@ -45,11 +49,11 @@ def async_trigger(hass, config, action): # If event data doesn't match requested schema, skip event return - hass.async_run_job(action({ - 'trigger': { - 'platform': 'event', - 'event': event, - }, - }, context=event.context)) + 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 b55d99f70..9470dde9c 100644 --- a/homeassistant/components/automation/homeassistant.py +++ b/homeassistant/components/automation/homeassistant.py @@ -10,17 +10,18 @@ import logging import voluptuous as vol from homeassistant.core import callback, CoreState -from homeassistant.const import ( - CONF_PLATFORM, CONF_EVENT, EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import CONF_PLATFORM, CONF_EVENT, EVENT_HOMEASSISTANT_STOP -EVENT_START = 'start' -EVENT_SHUTDOWN = 'shutdown' +EVENT_START = "start" +EVENT_SHUTDOWN = "shutdown" _LOGGER = logging.getLogger(__name__) -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'homeassistant', - vol.Required(CONF_EVENT): vol.Any(EVENT_START, EVENT_SHUTDOWN), -}) +TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "homeassistant", + vol.Required(CONF_EVENT): vol.Any(EVENT_START, EVENT_SHUTDOWN), + } +) @asyncio.coroutine @@ -29,27 +30,24 @@ def async_trigger(hass, config, action): event = config.get(CONF_EVENT) if event == EVENT_SHUTDOWN: + @callback def hass_shutdown(event): """Execute when Home Assistant is shutting down.""" - hass.async_run_job(action({ - 'trigger': { - 'platform': 'homeassistant', - 'event': event, - }, - }, context=event.context)) + hass.async_run_job( + action( + {"trigger": {"platform": "homeassistant", "event": event}}, + context=event.context, + ) + ) - return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, - hass_shutdown) + return hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, hass_shutdown) # 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({ - 'trigger': { - 'platform': 'homeassistant', - 'event': event, - }, - })) + hass.async_run_job( + action({"trigger": {"platform": "homeassistant", "event": event}}) + ) return lambda: None diff --git a/homeassistant/components/automation/litejet.py b/homeassistant/components/automation/litejet.py index c827fe8f7..cabf42f0c 100644 --- a/homeassistant/components/automation/litejet.py +++ b/homeassistant/components/automation/litejet.py @@ -15,22 +15,26 @@ import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util from homeassistant.helpers.event import track_point_in_utc_time -DEPENDENCIES = ['litejet'] +DEPENDENCIES = ["litejet"] _LOGGER = logging.getLogger(__name__) -CONF_NUMBER = 'number' -CONF_HELD_MORE_THAN = 'held_more_than' -CONF_HELD_LESS_THAN = 'held_less_than' +CONF_NUMBER = "number" +CONF_HELD_MORE_THAN = "held_more_than" +CONF_HELD_LESS_THAN = "held_less_than" -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'litejet', - vol.Required(CONF_NUMBER): cv.positive_int, - vol.Optional(CONF_HELD_MORE_THAN): - vol.All(cv.time_period, cv.positive_timedelta), - vol.Optional(CONF_HELD_LESS_THAN): - vol.All(cv.time_period, cv.positive_timedelta) -}) +TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "litejet", + vol.Required(CONF_NUMBER): cv.positive_int, + vol.Optional(CONF_HELD_MORE_THAN): vol.All( + cv.time_period, cv.positive_timedelta + ), + vol.Optional(CONF_HELD_LESS_THAN): vol.All( + cv.time_period, cv.positive_timedelta + ), + } +) @asyncio.coroutine @@ -45,14 +49,17 @@ def async_trigger(hass, config, action): @callback def call_action(): """Call action with right context.""" - hass.async_run_job(action, { - 'trigger': { - CONF_PLATFORM: 'litejet', - CONF_NUMBER: number, - CONF_HELD_MORE_THAN: held_more_than, - CONF_HELD_LESS_THAN: held_less_than + hass.async_run_job( + action, + { + "trigger": { + CONF_PLATFORM: "litejet", + CONF_NUMBER: number, + CONF_HELD_MORE_THAN: held_more_than, + CONF_HELD_LESS_THAN: held_less_than, + } }, - }) + ) # held_more_than and held_less_than: trigger on released (if in time range) # held_more_than: trigger after pressed with calculation @@ -73,9 +80,8 @@ def async_trigger(hass, config, action): hass.add_job(call_action) if held_more_than is not None and held_less_than is None: cancel_pressed_more_than = track_point_in_utc_time( - hass, - pressed_more_than_satisfied, - dt_util.utcnow() + held_more_than) + hass, pressed_more_than_satisfied, dt_util.utcnow() + held_more_than + ) def released(): """Handle the release of the LiteJet switch's button.""" @@ -90,8 +96,8 @@ def async_trigger(hass, config, action): if held_more_than is None or held_time > held_more_than: hass.add_job(call_action) - hass.data['litejet_system'].on_switch_pressed(number, pressed) - hass.data['litejet_system'].on_switch_released(number, released) + hass.data["litejet_system"].on_switch_pressed(number, pressed) + hass.data["litejet_system"].on_switch_released(number, released) def async_remove(): """Remove all subscriptions used for this trigger.""" diff --git a/homeassistant/components/automation/mqtt.py b/homeassistant/components/automation/mqtt.py index 60c33ca9b..60951ff46 100644 --- a/homeassistant/components/automation/mqtt.py +++ b/homeassistant/components/automation/mqtt.py @@ -11,18 +11,20 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.components import mqtt -from homeassistant.const import (CONF_PLATFORM, CONF_PAYLOAD) +from homeassistant.const import CONF_PLATFORM, CONF_PAYLOAD import homeassistant.helpers.config_validation as cv -DEPENDENCIES = ['mqtt'] +DEPENDENCIES = ["mqtt"] -CONF_TOPIC = 'topic' +CONF_TOPIC = "topic" -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): mqtt.DOMAIN, - vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_PAYLOAD): cv.string, -}) +TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): mqtt.DOMAIN, + vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_PAYLOAD): cv.string, + } +) @asyncio.coroutine @@ -36,21 +38,18 @@ def async_trigger(hass, config, action): """Listen for MQTT messages.""" if payload is None or payload == msg_payload: data = { - 'platform': 'mqtt', - 'topic': msg_topic, - 'payload': msg_payload, - 'qos': qos, + "platform": "mqtt", + "topic": msg_topic, + "payload": msg_payload, + "qos": qos, } try: - data['payload_json'] = json.loads(msg_payload) + data["payload_json"] = json.loads(msg_payload) except ValueError: pass - hass.async_run_job(action, { - 'trigger': data - }) + hass.async_run_job(action, {"trigger": data}) - remove = yield from mqtt.async_subscribe( - hass, topic, mqtt_automation_listener) + remove = yield from mqtt.async_subscribe(hass, topic, mqtt_automation_listener) return remove diff --git a/homeassistant/components/automation/numeric_state.py b/homeassistant/components/automation/numeric_state.py index f0dcbf0be..973fa93b6 100644 --- a/homeassistant/components/automation/numeric_state.py +++ b/homeassistant/components/automation/numeric_state.py @@ -11,20 +11,29 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.const import ( - CONF_VALUE_TEMPLATE, CONF_PLATFORM, CONF_ENTITY_ID, - CONF_BELOW, CONF_ABOVE, CONF_FOR) -from homeassistant.helpers.event import ( - async_track_state_change, async_track_same_state) + CONF_VALUE_TEMPLATE, + CONF_PLATFORM, + CONF_ENTITY_ID, + CONF_BELOW, + CONF_ABOVE, + CONF_FOR, +) +from homeassistant.helpers.event import async_track_state_change, async_track_same_state from homeassistant.helpers import condition, config_validation as cv -TRIGGER_SCHEMA = vol.All(vol.Schema({ - vol.Required(CONF_PLATFORM): 'numeric_state', - vol.Required(CONF_ENTITY_ID): cv.entity_ids, - vol.Optional(CONF_BELOW): vol.Coerce(float), - vol.Optional(CONF_ABOVE): vol.Coerce(float), - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_FOR): vol.All(cv.time_period, cv.positive_timedelta), -}), cv.has_at_least_one_key(CONF_BELOW, CONF_ABOVE)) +TRIGGER_SCHEMA = vol.All( + vol.Schema( + { + vol.Required(CONF_PLATFORM): "numeric_state", + vol.Required(CONF_ENTITY_ID): cv.entity_ids, + vol.Optional(CONF_BELOW): vol.Coerce(float), + vol.Optional(CONF_ABOVE): vol.Coerce(float), + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_FOR): vol.All(cv.time_period, cv.positive_timedelta), + } + ), + cv.has_at_least_one_key(CONF_BELOW, CONF_ABOVE), +) _LOGGER = logging.getLogger(__name__) @@ -50,32 +59,39 @@ def async_trigger(hass, config, action): return False variables = { - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': entity, - 'below': below, - 'above': above, + "trigger": { + "platform": "numeric_state", + "entity_id": entity, + "below": below, + "above": above, } } return condition.async_numeric_state( - hass, to_s, below, above, value_template, variables) + hass, to_s, below, above, value_template, variables + ) @callback def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" + @callback def call_action(): """Call action with right context.""" - hass.async_run_job(action({ - 'trigger': { - 'platform': 'numeric_state', - 'entity_id': entity, - 'below': below, - 'above': above, - 'from_state': from_s, - 'to_state': to_s, - } - }, context=to_s.context)) + hass.async_run_job( + action( + { + "trigger": { + "platform": "numeric_state", + "entity_id": entity, + "below": below, + "above": above, + "from_state": from_s, + "to_state": to_s, + } + }, + context=to_s.context, + ) + ) matching = check_numeric_state(entity, from_s, to_s) @@ -86,13 +102,16 @@ def async_trigger(hass, config, action): if time_delta: unsub_track_same[entity] = async_track_same_state( - hass, time_delta, call_action, entity_ids=entity_id, - async_check_same_func=check_numeric_state) + hass, + time_delta, + call_action, + entity_ids=entity_id, + async_check_same_func=check_numeric_state, + ) else: call_action() - unsub = async_track_state_change( - hass, entity_id, state_automation_listener) + unsub = async_track_state_change(hass, entity_id, state_automation_listener) @callback def async_remove(): diff --git a/homeassistant/components/automation/state.py b/homeassistant/components/automation/state.py index 263d4158e..08d6a3d2e 100644 --- a/homeassistant/components/automation/state.py +++ b/homeassistant/components/automation/state.py @@ -9,22 +9,26 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.const import MATCH_ALL, CONF_PLATFORM, CONF_FOR -from homeassistant.helpers.event import ( - async_track_state_change, async_track_same_state) +from homeassistant.helpers.event import async_track_state_change, async_track_same_state import homeassistant.helpers.config_validation as cv -CONF_ENTITY_ID = 'entity_id' -CONF_FROM = 'from' -CONF_TO = 'to' +CONF_ENTITY_ID = "entity_id" +CONF_FROM = "from" +CONF_TO = "to" -TRIGGER_SCHEMA = vol.All(vol.Schema({ - vol.Required(CONF_PLATFORM): 'state', - vol.Required(CONF_ENTITY_ID): cv.entity_ids, - # These are str on purpose. Want to catch YAML conversions - vol.Optional(CONF_FROM): str, - vol.Optional(CONF_TO): str, - vol.Optional(CONF_FOR): vol.All(cv.time_period, cv.positive_timedelta), -}), cv.key_dependency(CONF_FOR, CONF_TO)) +TRIGGER_SCHEMA = vol.All( + vol.Schema( + { + vol.Required(CONF_PLATFORM): "state", + vol.Required(CONF_ENTITY_ID): cv.entity_ids, + # These are str on purpose. Want to catch YAML conversions + vol.Optional(CONF_FROM): str, + vol.Optional(CONF_TO): str, + vol.Optional(CONF_FOR): vol.All(cv.time_period, cv.positive_timedelta), + } + ), + cv.key_dependency(CONF_FOR, CONF_TO), +) @asyncio.coroutine @@ -34,28 +38,38 @@ def async_trigger(hass, config, action): from_state = config.get(CONF_FROM, MATCH_ALL) to_state = config.get(CONF_TO, MATCH_ALL) time_delta = config.get(CONF_FOR) - match_all = (from_state == MATCH_ALL and to_state == MATCH_ALL) + match_all = from_state == MATCH_ALL and to_state == MATCH_ALL unsub_track_same = {} @callback def state_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" + @callback def call_action(): """Call action with right context.""" - hass.async_run_job(action({ - 'trigger': { - 'platform': 'state', - 'entity_id': entity, - 'from_state': from_s, - 'to_state': to_s, - 'for': time_delta, - } - }, context=to_s.context)) + hass.async_run_job( + action( + { + "trigger": { + "platform": "state", + "entity_id": entity, + "from_state": from_s, + "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 - from_s.state == to_s.state): + if ( + not match_all + and from_s is not None + and to_s is not None + and from_s.state == to_s.state + ): return if not time_delta: @@ -63,12 +77,16 @@ def async_trigger(hass, config, action): return unsub_track_same[entity] = async_track_same_state( - hass, time_delta, call_action, + hass, + time_delta, + call_action, lambda _, _2, to_state: to_state.state == to_s.state, - entity_ids=entity_id) + entity_ids=entity_id, + ) unsub = async_track_state_change( - hass, entity_id, state_automation_listener, from_state, to_state) + hass, entity_id, state_automation_listener, from_state, to_state + ) @callback def async_remove(): diff --git a/homeassistant/components/automation/sun.py b/homeassistant/components/automation/sun.py index 497b84532..cb322bfcb 100644 --- a/homeassistant/components/automation/sun.py +++ b/homeassistant/components/automation/sun.py @@ -12,17 +12,23 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.const import ( - CONF_EVENT, CONF_OFFSET, CONF_PLATFORM, SUN_EVENT_SUNRISE) + CONF_EVENT, + CONF_OFFSET, + CONF_PLATFORM, + SUN_EVENT_SUNRISE, +) from homeassistant.helpers.event import async_track_sunrise, async_track_sunset import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'sun', - vol.Required(CONF_EVENT): cv.sun_event, - vol.Required(CONF_OFFSET, default=timedelta(0)): cv.time_period, -}) +TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "sun", + vol.Required(CONF_EVENT): cv.sun_event, + vol.Required(CONF_OFFSET, default=timedelta(0)): cv.time_period, + } +) @asyncio.coroutine @@ -34,13 +40,9 @@ def async_trigger(hass, config, action): @callback def call_action(): """Call action with right context.""" - hass.async_run_job(action, { - 'trigger': { - 'platform': 'sun', - 'event': event, - 'offset': offset, - }, - }) + hass.async_run_job( + action, {"trigger": {"platform": "sun", "event": event, "offset": offset}} + ) if event == SUN_EVENT_SUNRISE: return async_track_sunrise(hass, call_action, offset) diff --git a/homeassistant/components/automation/template.py b/homeassistant/components/automation/template.py index 67a44f1a3..11ded715d 100644 --- a/homeassistant/components/automation/template.py +++ b/homeassistant/components/automation/template.py @@ -17,10 +17,12 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'template', - vol.Required(CONF_VALUE_TEMPLATE): cv.template, -}) +TRIGGER_SCHEMA = IF_ACTION_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "template", + vol.Required(CONF_VALUE_TEMPLATE): cv.template, + } +) @asyncio.coroutine @@ -32,13 +34,18 @@ 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({ - 'trigger': { - 'platform': 'template', - 'entity_id': entity_id, - 'from_state': from_s, - 'to_state': to_s, - }, - }, context=to_s.context)) + 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/time.py b/homeassistant/components/automation/time.py index a3a8496c3..463df23a9 100644 --- a/homeassistant/components/automation/time.py +++ b/homeassistant/components/automation/time.py @@ -14,19 +14,24 @@ from homeassistant.const import CONF_AT, CONF_PLATFORM from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import async_track_time_change -CONF_HOURS = 'hours' -CONF_MINUTES = 'minutes' -CONF_SECONDS = 'seconds' +CONF_HOURS = "hours" +CONF_MINUTES = "minutes" +CONF_SECONDS = "seconds" _LOGGER = logging.getLogger(__name__) -TRIGGER_SCHEMA = vol.All(vol.Schema({ - vol.Required(CONF_PLATFORM): 'time', - CONF_AT: cv.time, - CONF_HOURS: vol.Any(vol.Coerce(int), vol.Coerce(str)), - CONF_MINUTES: vol.Any(vol.Coerce(int), vol.Coerce(str)), - CONF_SECONDS: vol.Any(vol.Coerce(int), vol.Coerce(str)), -}), cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AT)) +TRIGGER_SCHEMA = vol.All( + vol.Schema( + { + vol.Required(CONF_PLATFORM): "time", + CONF_AT: cv.time, + CONF_HOURS: vol.Any(vol.Coerce(int), vol.Coerce(str)), + CONF_MINUTES: vol.Any(vol.Coerce(int), vol.Coerce(str)), + CONF_SECONDS: vol.Any(vol.Coerce(int), vol.Coerce(str)), + } + ), + cv.has_at_least_one_key(CONF_HOURS, CONF_MINUTES, CONF_SECONDS, CONF_AT), +) @asyncio.coroutine @@ -43,12 +48,8 @@ def async_trigger(hass, config, action): @callback def time_automation_listener(now): """Listen for time changes and calls action.""" - hass.async_run_job(action, { - 'trigger': { - 'platform': 'time', - 'now': now, - }, - }) + hass.async_run_job(action, {"trigger": {"platform": "time", "now": now}}) - return async_track_time_change(hass, time_automation_listener, - hour=hours, minute=minutes, second=seconds) + return async_track_time_change( + hass, time_automation_listener, hour=hours, minute=minutes, second=seconds + ) diff --git a/homeassistant/components/automation/zone.py b/homeassistant/components/automation/zone.py index f30dfe753..58acedcd2 100644 --- a/homeassistant/components/automation/zone.py +++ b/homeassistant/components/automation/zone.py @@ -9,22 +9,29 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.const import ( - CONF_EVENT, CONF_ENTITY_ID, CONF_ZONE, MATCH_ALL, CONF_PLATFORM) + CONF_EVENT, + CONF_ENTITY_ID, + CONF_ZONE, + MATCH_ALL, + CONF_PLATFORM, +) from homeassistant.helpers.event import async_track_state_change -from homeassistant.helpers import ( - condition, config_validation as cv, location) +from homeassistant.helpers import condition, config_validation as cv, location -EVENT_ENTER = 'enter' -EVENT_LEAVE = 'leave' +EVENT_ENTER = "enter" +EVENT_LEAVE = "leave" DEFAULT_EVENT = EVENT_ENTER -TRIGGER_SCHEMA = vol.Schema({ - vol.Required(CONF_PLATFORM): 'zone', - vol.Required(CONF_ENTITY_ID): cv.entity_ids, - vol.Required(CONF_ZONE): cv.entity_id, - vol.Required(CONF_EVENT, default=DEFAULT_EVENT): - vol.Any(EVENT_ENTER, EVENT_LEAVE), -}) +TRIGGER_SCHEMA = vol.Schema( + { + vol.Required(CONF_PLATFORM): "zone", + vol.Required(CONF_ENTITY_ID): cv.entity_ids, + vol.Required(CONF_ZONE): cv.entity_id, + vol.Required(CONF_EVENT, default=DEFAULT_EVENT): vol.Any( + EVENT_ENTER, EVENT_LEAVE + ), + } +) @asyncio.coroutine @@ -37,8 +44,11 @@ def async_trigger(hass, config, action): @callback def zone_automation_listener(entity, from_s, to_s): """Listen for state changes and calls action.""" - if from_s and not location.has_location(from_s) or \ - not location.has_location(to_s): + if ( + from_s + and not location.has_location(from_s) + or not location.has_location(to_s) + ): return zone_state = hass.states.get(zone_entity_id) @@ -49,18 +59,30 @@ def async_trigger(hass, config, action): to_match = condition.zone(hass, zone_state, to_s) # 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({ - 'trigger': { - 'platform': 'zone', - 'entity_id': entity, - 'from_state': from_s, - 'to_state': to_s, - 'zone': zone_state, - 'event': event, - }, - }, context=to_s.context)) + 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( + { + "trigger": { + "platform": "zone", + "entity_id": entity, + "from_state": from_s, + "to_state": to_s, + "zone": zone_state, + "event": event, + } + }, + context=to_s.context, + ) + ) - return async_track_state_change(hass, entity_id, zone_automation_listener, - MATCH_ALL, MATCH_ALL) + return async_track_state_change( + hass, entity_id, zone_automation_listener, MATCH_ALL, MATCH_ALL + ) diff --git a/homeassistant/components/axis.py b/homeassistant/components/axis.py index 71894364f..d2c8e7ab9 100644 --- a/homeassistant/components/axis.py +++ b/homeassistant/components/axis.py @@ -10,67 +10,76 @@ import voluptuous as vol from homeassistant.components.discovery import SERVICE_AXIS from homeassistant.const import ( - ATTR_LOCATION, ATTR_TRIPPED, CONF_EVENT, CONF_HOST, CONF_INCLUDE, - CONF_NAME, CONF_PASSWORD, CONF_PORT, CONF_TRIGGER_TIME, CONF_USERNAME, - EVENT_HOMEASSISTANT_STOP) + ATTR_LOCATION, + ATTR_TRIPPED, + CONF_EVENT, + CONF_HOST, + CONF_INCLUDE, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_TRIGGER_TIME, + CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers import config_validation as cv from homeassistant.helpers import discovery from homeassistant.helpers.dispatcher import dispatcher_send from homeassistant.helpers.entity import Entity from homeassistant.util.json import load_json, save_json -REQUIREMENTS = ['axis==14'] +REQUIREMENTS = ["axis==14"] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'axis' -CONFIG_FILE = 'axis.conf' +DOMAIN = "axis" +CONFIG_FILE = "axis.conf" AXIS_DEVICES = {} -EVENT_TYPES = ['motion', 'vmd3', 'pir', 'sound', - 'daynight', 'tampering', 'input'] +EVENT_TYPES = ["motion", "vmd3", "pir", "sound", "daynight", "tampering", "input"] -PLATFORMS = ['camera'] +PLATFORMS = ["camera"] AXIS_INCLUDE = EVENT_TYPES + PLATFORMS -AXIS_DEFAULT_HOST = '192.168.0.90' -AXIS_DEFAULT_USERNAME = 'root' -AXIS_DEFAULT_PASSWORD = 'pass' +AXIS_DEFAULT_HOST = "192.168.0.90" +AXIS_DEFAULT_USERNAME = "root" +AXIS_DEFAULT_PASSWORD = "pass" -DEVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_INCLUDE): - vol.All(cv.ensure_list, [vol.In(AXIS_INCLUDE)]), - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_HOST, default=AXIS_DEFAULT_HOST): cv.string, - vol.Optional(CONF_USERNAME, default=AXIS_DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD, default=AXIS_DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_TRIGGER_TIME, default=0): cv.positive_int, - vol.Optional(CONF_PORT, default=80): cv.positive_int, - vol.Optional(ATTR_LOCATION, default=''): cv.string, -}) +DEVICE_SCHEMA = vol.Schema( + { + vol.Required(CONF_INCLUDE): vol.All(cv.ensure_list, [vol.In(AXIS_INCLUDE)]), + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_HOST, default=AXIS_DEFAULT_HOST): cv.string, + vol.Optional(CONF_USERNAME, default=AXIS_DEFAULT_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD, default=AXIS_DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_TRIGGER_TIME, default=0): cv.positive_int, + vol.Optional(CONF_PORT, default=80): cv.positive_int, + vol.Optional(ATTR_LOCATION, default=""): cv.string, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: DEVICE_SCHEMA, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({cv.slug: DEVICE_SCHEMA})}, extra=vol.ALLOW_EXTRA +) -SERVICE_VAPIX_CALL = 'vapix_call' -SERVICE_VAPIX_CALL_RESPONSE = 'vapix_call_response' -SERVICE_CGI = 'cgi' -SERVICE_ACTION = 'action' -SERVICE_PARAM = 'param' -SERVICE_DEFAULT_CGI = 'param.cgi' -SERVICE_DEFAULT_ACTION = 'update' +SERVICE_VAPIX_CALL = "vapix_call" +SERVICE_VAPIX_CALL_RESPONSE = "vapix_call_response" +SERVICE_CGI = "cgi" +SERVICE_ACTION = "action" +SERVICE_PARAM = "param" +SERVICE_DEFAULT_CGI = "param.cgi" +SERVICE_DEFAULT_ACTION = "update" -SERVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Required(SERVICE_PARAM): cv.string, - vol.Optional(SERVICE_CGI, default=SERVICE_DEFAULT_CGI): cv.string, - vol.Optional(SERVICE_ACTION, default=SERVICE_DEFAULT_ACTION): cv.string, -}) +SERVICE_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(SERVICE_PARAM): cv.string, + vol.Optional(SERVICE_CGI, default=SERVICE_DEFAULT_CGI): cv.string, + vol.Optional(SERVICE_ACTION, default=SERVICE_DEFAULT_ACTION): cv.string, + } +) def request_configuration(hass, config, name, host, serialnumber): @@ -80,8 +89,7 @@ def request_configuration(hass, config, name, host, serialnumber): def configuration_callback(callback_data): """Call when configuration is submitted.""" if CONF_INCLUDE not in callback_data: - configurator.notify_errors( - request_id, "Functionality mandatory.") + configurator.notify_errors(request_id, "Functionality mandatory.") return False callback_data[CONF_INCLUDE] = callback_data[CONF_INCLUDE].split() @@ -93,58 +101,58 @@ def request_configuration(hass, config, name, host, serialnumber): try: device_config = DEVICE_SCHEMA(callback_data) except vol.Invalid: - configurator.notify_errors( - request_id, "Bad input, please check spelling.") + configurator.notify_errors(request_id, "Bad input, please check spelling.") return False if setup_device(hass, config, device_config): - del device_config['events'] - del device_config['signal'] + del device_config["events"] + del device_config["signal"] config_file = load_json(hass.config.path(CONFIG_FILE)) config_file[serialnumber] = dict(device_config) save_json(hass.config.path(CONFIG_FILE), config_file) configurator.request_done(request_id) else: configurator.notify_errors( - request_id, "Failed to register, please try again.") + request_id, "Failed to register, please try again." + ) return False - title = '{} ({})'.format(name, host) + title = "{} ({})".format(name, host) request_id = configurator.request_config( - title, configuration_callback, - description='Functionality: ' + str(AXIS_INCLUDE), + title, + configuration_callback, + description="Functionality: " + str(AXIS_INCLUDE), entity_picture="/static/images/logo_axis.png", - link_name='Axis platform documentation', - link_url='https://home-assistant.io/components/axis/', + link_name="Axis platform documentation", + link_url="https://home-assistant.io/components/axis/", submit_caption="Confirm", fields=[ - {'id': CONF_NAME, - 'name': "Device name", - 'type': 'text'}, - {'id': CONF_USERNAME, - 'name': "User name", - 'type': 'text'}, - {'id': CONF_PASSWORD, - 'name': 'Password', - 'type': 'password'}, - {'id': CONF_INCLUDE, - 'name': "Device functionality (space separated list)", - 'type': 'text'}, - {'id': ATTR_LOCATION, - 'name': "Physical location of device (optional)", - 'type': 'text'}, - {'id': CONF_PORT, - 'name': "HTTP port (default=80)", - 'type': 'number'}, - {'id': CONF_TRIGGER_TIME, - 'name': "Sensor update interval (optional)", - 'type': 'number'}, - ] + {"id": CONF_NAME, "name": "Device name", "type": "text"}, + {"id": CONF_USERNAME, "name": "User name", "type": "text"}, + {"id": CONF_PASSWORD, "name": "Password", "type": "password"}, + { + "id": CONF_INCLUDE, + "name": "Device functionality (space separated list)", + "type": "text", + }, + { + "id": ATTR_LOCATION, + "name": "Physical location of device (optional)", + "type": "text", + }, + {"id": CONF_PORT, "name": "HTTP port (default=80)", "type": "number"}, + { + "id": CONF_TRIGGER_TIME, + "name": "Sensor update interval (optional)", + "type": "number", + }, + ], ) def setup(hass, config): """Set up for Axis devices.""" + def _shutdown(call): """Stop the event stream on shutdown.""" for serialnumber, device in AXIS_DEVICES.items(): @@ -156,8 +164,8 @@ def setup(hass, config): def axis_device_discovered(service, discovery_info): """Call when axis devices has been found.""" host = discovery_info[CONF_HOST] - name = discovery_info['hostname'] - serialnumber = discovery_info['properties']['macaddress'] + name = discovery_info["hostname"] + serialnumber = discovery_info["properties"]["macaddress"] if serialnumber not in AXIS_DEVICES: config_file = load_json(hass.config.path(CONFIG_FILE)) @@ -170,8 +178,7 @@ def setup(hass, config): _LOGGER.error("Bad data from %s. %s", CONFIG_FILE, err) return False if not setup_device(hass, config, device_config): - _LOGGER.error( - "Couldn't set up %s", device_config[CONF_NAME]) + _LOGGER.error("Couldn't set up %s", device_config[CONF_NAME]) else: # New device, create configuration request for UI request_configuration(hass, config, name, host, serialnumber) @@ -179,7 +186,7 @@ def setup(hass, config): # Device already registered, but on a different IP device = AXIS_DEVICES[serialnumber] device.config.host = host - dispatcher_send(hass, DOMAIN + '_' + device.name + '_new_ip', host) + dispatcher_send(hass, DOMAIN + "_" + device.name + "_new_ip", host) # Register discovery service discovery.listen(hass, SERVICE_AXIS, axis_device_discovered) @@ -199,7 +206,8 @@ def setup(hass, config): response = device.vapix.do_request( call.data[SERVICE_CGI], call.data[SERVICE_ACTION], - call.data[SERVICE_PARAM]) + call.data[SERVICE_PARAM], + ) hass.bus.fire(SERVICE_VAPIX_CALL_RESPONSE, response) return True _LOGGER.info("Couldn't find device %s", call.data[CONF_NAME]) @@ -207,7 +215,8 @@ def setup(hass, config): # Register service with Home Assistant. hass.services.register( - DOMAIN, SERVICE_VAPIX_CALL, vapix_service, schema=SERVICE_SCHEMA) + DOMAIN, SERVICE_VAPIX_CALL, vapix_service, schema=SERVICE_SCHEMA + ) return True @@ -217,21 +226,19 @@ def setup_device(hass, config, device_config): def signal_callback(action, event): """Call to configure events when initialized on event stream.""" - if action == 'add': + if action == "add": event_config = { CONF_EVENT: event, CONF_NAME: device_config[CONF_NAME], ATTR_LOCATION: device_config[ATTR_LOCATION], - CONF_TRIGGER_TIME: device_config[CONF_TRIGGER_TIME] + CONF_TRIGGER_TIME: device_config[CONF_TRIGGER_TIME], } component = event.event_platform - discovery.load_platform( - hass, component, DOMAIN, event_config, config) + discovery.load_platform(hass, component, DOMAIN, event_config, config) - event_types = list(filter(lambda x: x in device_config[CONF_INCLUDE], - EVENT_TYPES)) - device_config['events'] = event_types - device_config['signal'] = signal_callback + event_types = list(filter(lambda x: x in device_config[CONF_INCLUDE], EVENT_TYPES)) + device_config["events"] = event_types + device_config["signal"] = signal_callback device = AxisDevice(hass.loop, **device_config) device.name = device_config[CONF_NAME] @@ -241,16 +248,15 @@ def setup_device(hass, config, device_config): return False for component in device_config[CONF_INCLUDE]: - if component == 'camera': + if component == "camera": camera_config = { CONF_NAME: device_config[CONF_NAME], CONF_HOST: device_config[CONF_HOST], CONF_PORT: device_config[CONF_PORT], CONF_USERNAME: device_config[CONF_USERNAME], - CONF_PASSWORD: device_config[CONF_PASSWORD] + CONF_PASSWORD: device_config[CONF_PASSWORD], } - discovery.load_platform( - hass, component, DOMAIN, camera_config, config) + discovery.load_platform(hass, component, DOMAIN, camera_config, config) AXIS_DEVICES[device.serial_number] = device if event_types: @@ -264,9 +270,9 @@ class AxisDeviceEvent(Entity): def __init__(self, event_config): """Initialize the event.""" self.axis_event = event_config[CONF_EVENT] - self._name = '{}_{}_{}'.format( - event_config[CONF_NAME], self.axis_event.event_type, - self.axis_event.id) + self._name = "{}_{}_{}".format( + event_config[CONF_NAME], self.axis_event.event_type, self.axis_event.id + ) self.location = event_config[ATTR_LOCATION] self.axis_event.callback = self._update_callback @@ -295,7 +301,7 @@ class AxisDeviceEvent(Entity): attr = {} tripped = self.axis_event.is_tripped - attr[ATTR_TRIPPED] = 'True' if tripped else 'False' + attr[ATTR_TRIPPED] = "True" if tripped else "False" attr[ATTR_LOCATION] = self.location diff --git a/homeassistant/components/bbb_gpio.py b/homeassistant/components/bbb_gpio.py index e3f327f1d..a5d0d5770 100644 --- a/homeassistant/components/bbb_gpio.py +++ b/homeassistant/components/bbb_gpio.py @@ -6,14 +6,13 @@ https://home-assistant.io/components/bbb_gpio/ """ import logging -from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP) +from homeassistant.const import EVENT_HOMEASSISTANT_START, EVENT_HOMEASSISTANT_STOP -REQUIREMENTS = ['Adafruit_BBIO==1.0.0'] +REQUIREMENTS = ["Adafruit_BBIO==1.0.0"] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'bbb_gpio' +DOMAIN = "bbb_gpio" def setup(hass, config): @@ -37,6 +36,7 @@ def setup_output(pin): """Set up a GPIO as output.""" # pylint: disable=import-error from Adafruit_BBIO import GPIO + GPIO.setup(pin, GPIO.OUT) @@ -44,15 +44,15 @@ def setup_input(pin, pull_mode): """Set up a GPIO as input.""" # pylint: disable=import-error from Adafruit_BBIO import GPIO - GPIO.setup(pin, GPIO.IN, - GPIO.PUD_DOWN if pull_mode == 'DOWN' - else GPIO.PUD_UP) + + GPIO.setup(pin, GPIO.IN, GPIO.PUD_DOWN if pull_mode == "DOWN" else GPIO.PUD_UP) def write_output(pin, value): """Write a value to a GPIO.""" # pylint: disable=import-error from Adafruit_BBIO import GPIO + GPIO.output(pin, value) @@ -60,6 +60,7 @@ def read_input(pin): """Read a value from a GPIO.""" # pylint: disable=import-error from Adafruit_BBIO import GPIO + return GPIO.input(pin) is GPIO.HIGH @@ -67,5 +68,5 @@ def edge_detect(pin, event_callback, bounce): """Add detection for RISING and FALLING events.""" # pylint: disable=import-error from Adafruit_BBIO import GPIO - GPIO.add_event_detect( - pin, GPIO.BOTH, callback=event_callback, bouncetime=bounce) + + GPIO.add_event_detect(pin, GPIO.BOTH, callback=event_callback, bouncetime=bounce) diff --git a/homeassistant/components/binary_sensor/__init__.py b/homeassistant/components/binary_sensor/__init__.py index 7b2da21ff..3a1e94c59 100644 --- a/homeassistant/components/binary_sensor/__init__.py +++ b/homeassistant/components/binary_sensor/__init__.py @@ -12,37 +12,37 @@ import voluptuous as vol from homeassistant.helpers.entity_component import EntityComponent from homeassistant.helpers.entity import Entity -from homeassistant.const import (STATE_ON, STATE_OFF) +from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa -DOMAIN = 'binary_sensor' +DOMAIN = "binary_sensor" SCAN_INTERVAL = timedelta(seconds=30) -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" DEVICE_CLASSES = [ - 'battery', # On means low, Off means normal - 'cold', # On means cold, Off means normal - 'connectivity', # On means connected, Off means disconnected - 'door', # On means open, Off means closed - 'garage_door', # On means open, Off means closed - 'gas', # On means gas detected, Off means no gas (clear) - 'heat', # On means hot, Off means normal - 'light', # On means light detected, Off means no light - 'lock', # On means open (unlocked), Off means closed (locked) - 'moisture', # On means wet, Off means dry - 'motion', # On means motion detected, Off means no motion (clear) - 'moving', # On means moving, Off means not moving (stopped) - 'occupancy', # On means occupied, Off means not occupied (clear) - 'opening', # On means open, Off means closed - 'plug', # On means plugged in, Off means unplugged - 'power', # On means power detected, Off means no power - 'presence', # On means home, Off means away - 'problem', # On means problem detected, Off means no problem (OK) - 'safety', # On means unsafe, Off means safe - 'smoke', # On means smoke detected, Off means no smoke (clear) - 'sound', # On means sound detected, Off means no sound (clear) - 'vibration', # On means vibration detected, Off means no vibration - 'window', # On means open, Off means closed + "battery", # On means low, Off means normal + "cold", # On means cold, Off means normal + "connectivity", # On means connected, Off means disconnected + "door", # On means open, Off means closed + "garage_door", # On means open, Off means closed + "gas", # On means gas detected, Off means no gas (clear) + "heat", # On means hot, Off means normal + "light", # On means light detected, Off means no light + "lock", # On means open (unlocked), Off means closed (locked) + "moisture", # On means wet, Off means dry + "motion", # On means motion detected, Off means no motion (clear) + "moving", # On means moving, Off means not moving (stopped) + "occupancy", # On means occupied, Off means not occupied (clear) + "opening", # On means open, Off means closed + "plug", # On means plugged in, Off means unplugged + "power", # On means power detected, Off means no power + "presence", # On means home, Off means away + "problem", # On means problem detected, Off means no problem (OK) + "safety", # On means unsafe, Off means safe + "smoke", # On means smoke detected, Off means no smoke (clear) + "sound", # On means sound detected, Off means no sound (clear) + "vibration", # On means vibration detected, Off means no vibration + "window", # On means open, Off means closed ] DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES)) @@ -51,7 +51,8 @@ DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES)) async def async_setup(hass, config): """Track states and offer events for binary sensors.""" component = hass.data[DOMAIN] = EntityComponent( - logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL) + logging.getLogger(__name__), DOMAIN, hass, SCAN_INTERVAL + ) await component.async_setup(config) return True diff --git a/homeassistant/components/binary_sensor/abode.py b/homeassistant/components/binary_sensor/abode.py index a821abf44..8393116f9 100644 --- a/homeassistant/components/binary_sensor/abode.py +++ b/homeassistant/components/binary_sensor/abode.py @@ -6,12 +6,15 @@ https://home-assistant.io/components/binary_sensor.abode/ """ import logging -from homeassistant.components.abode import (AbodeDevice, AbodeAutomation, - DOMAIN as ABODE_DOMAIN) +from homeassistant.components.abode import ( + AbodeDevice, + AbodeAutomation, + DOMAIN as ABODE_DOMAIN, +) from homeassistant.components.binary_sensor import BinarySensorDevice -DEPENDENCIES = ['abode'] +DEPENDENCIES = ["abode"] _LOGGER = logging.getLogger(__name__) @@ -23,9 +26,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): data = hass.data[ABODE_DOMAIN] - device_types = [CONST.TYPE_CONNECTIVITY, CONST.TYPE_MOISTURE, - CONST.TYPE_MOTION, CONST.TYPE_OCCUPANCY, - CONST.TYPE_OPENING] + device_types = [ + CONST.TYPE_CONNECTIVITY, + CONST.TYPE_MOISTURE, + CONST.TYPE_MOTION, + CONST.TYPE_OCCUPANCY, + CONST.TYPE_OPENING, + ] devices = [] for device in data.abode.get_devices(generic_type=device_types): @@ -34,13 +41,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): devices.append(AbodeBinarySensor(data, device)) - for automation in data.abode.get_automations( - generic_type=CONST.TYPE_QUICK_ACTION): + for automation in data.abode.get_automations(generic_type=CONST.TYPE_QUICK_ACTION): if data.is_automation_excluded(automation): continue - devices.append(AbodeQuickActionBinarySensor( - data, automation, TIMELINE.AUTOMATION_EDIT_GROUP)) + devices.append( + AbodeQuickActionBinarySensor( + data, automation, TIMELINE.AUTOMATION_EDIT_GROUP + ) + ) data.devices.extend(devices) diff --git a/homeassistant/components/binary_sensor/ads.py b/homeassistant/components/binary_sensor/ads.py index d46ff5ec2..67e22a6c6 100644 --- a/homeassistant/components/binary_sensor/ads.py +++ b/homeassistant/components/binary_sensor/ads.py @@ -11,20 +11,25 @@ import voluptuous as vol from homeassistant.components.ads import CONF_ADS_VAR, DATA_ADS from homeassistant.components.binary_sensor import ( - DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice) + DEVICE_CLASSES_SCHEMA, + PLATFORM_SCHEMA, + BinarySensorDevice, +) from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'ADS binary sensor' -DEPENDENCIES = ['ads'] +DEFAULT_NAME = "ADS binary sensor" +DEPENDENCIES = ["ads"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ADS_VAR): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ADS_VAR): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -46,22 +51,26 @@ class AdsBinarySensor(BinarySensorDevice): """Initialize ADS binary sensor.""" self._name = name self._state = False - self._device_class = device_class or 'moving' + self._device_class = device_class or "moving" self._ads_hub = ads_hub self.ads_var = ads_var @asyncio.coroutine def async_added_to_hass(self): """Register device notification.""" + def update(name, value): """Handle device notifications.""" - _LOGGER.debug('Variable %s changed its value to %d', name, value) + _LOGGER.debug("Variable %s changed its value to %d", name, value) self._state = value self.schedule_update_ha_state() self.hass.async_add_job( self._ads_hub.add_device_notification, - self.ads_var, self._ads_hub.PLCTYPE_BOOL, update) + self.ads_var, + self._ads_hub.PLCTYPE_BOOL, + update, + ) @property def name(self): diff --git a/homeassistant/components/binary_sensor/alarmdecoder.py b/homeassistant/components/binary_sensor/alarmdecoder.py index 82bcc5025..fcf13ea9e 100644 --- a/homeassistant/components/binary_sensor/alarmdecoder.py +++ b/homeassistant/components/binary_sensor/alarmdecoder.py @@ -9,23 +9,31 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.alarmdecoder import ( - ZONE_SCHEMA, CONF_ZONES, CONF_ZONE_NAME, CONF_ZONE_TYPE, - CONF_ZONE_RFID, SIGNAL_ZONE_FAULT, SIGNAL_ZONE_RESTORE, - SIGNAL_RFX_MESSAGE, SIGNAL_REL_MESSAGE, CONF_RELAY_ADDR, - CONF_RELAY_CHAN) + ZONE_SCHEMA, + CONF_ZONES, + CONF_ZONE_NAME, + CONF_ZONE_TYPE, + CONF_ZONE_RFID, + SIGNAL_ZONE_FAULT, + SIGNAL_ZONE_RESTORE, + SIGNAL_RFX_MESSAGE, + SIGNAL_REL_MESSAGE, + CONF_RELAY_ADDR, + CONF_RELAY_CHAN, +) -DEPENDENCIES = ['alarmdecoder'] +DEPENDENCIES = ["alarmdecoder"] _LOGGER = logging.getLogger(__name__) -ATTR_RF_BIT0 = 'rf_bit0' -ATTR_RF_LOW_BAT = 'rf_low_battery' -ATTR_RF_SUPERVISED = 'rf_supervised' -ATTR_RF_BIT3 = 'rf_bit3' -ATTR_RF_LOOP3 = 'rf_loop3' -ATTR_RF_LOOP2 = 'rf_loop2' -ATTR_RF_LOOP4 = 'rf_loop4' -ATTR_RF_LOOP1 = 'rf_loop1' +ATTR_RF_BIT0 = "rf_bit0" +ATTR_RF_LOW_BAT = "rf_low_battery" +ATTR_RF_SUPERVISED = "rf_supervised" +ATTR_RF_BIT3 = "rf_bit3" +ATTR_RF_LOOP3 = "rf_loop3" +ATTR_RF_LOOP2 = "rf_loop2" +ATTR_RF_LOOP4 = "rf_loop4" +ATTR_RF_LOOP1 = "rf_loop1" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -41,7 +49,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): relay_addr = device_config_data.get(CONF_RELAY_ADDR) relay_chan = device_config_data.get(CONF_RELAY_CHAN) device = AlarmDecoderBinarySensor( - zone_num, zone_name, zone_type, zone_rfid, relay_addr, relay_chan) + zone_num, zone_name, zone_type, zone_rfid, relay_addr, relay_chan + ) devices.append(device) add_entities(devices) @@ -52,8 +61,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class AlarmDecoderBinarySensor(BinarySensorDevice): """Representation of an AlarmDecoder binary sensor.""" - def __init__(self, zone_number, zone_name, zone_type, zone_rfid, - relay_addr, relay_chan): + def __init__( + self, zone_number, zone_name, zone_type, zone_rfid, relay_addr, relay_chan + ): """Initialize the binary_sensor.""" self._zone_number = zone_number self._zone_type = zone_type @@ -68,16 +78,20 @@ class AlarmDecoderBinarySensor(BinarySensorDevice): def async_added_to_hass(self): """Register callbacks.""" self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_ZONE_FAULT, self._fault_callback) + SIGNAL_ZONE_FAULT, self._fault_callback + ) self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_ZONE_RESTORE, self._restore_callback) + SIGNAL_ZONE_RESTORE, self._restore_callback + ) self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_RFX_MESSAGE, self._rfx_message_callback) + SIGNAL_RFX_MESSAGE, self._rfx_message_callback + ) self.hass.helpers.dispatcher.async_dispatcher_connect( - SIGNAL_REL_MESSAGE, self._rel_message_callback) + SIGNAL_REL_MESSAGE, self._rel_message_callback + ) @property def name(self): @@ -134,9 +148,9 @@ class AlarmDecoderBinarySensor(BinarySensorDevice): def _rel_message_callback(self, message): """Update relay state.""" - if (self._relay_addr == message.address and - self._relay_chan == message.channel): - _LOGGER.debug("Relay %d:%d value:%d", message.address, - message.channel, message.value) + if self._relay_addr == message.address and self._relay_chan == message.channel: + _LOGGER.debug( + "Relay %d:%d value:%d", message.address, message.channel, message.value + ) self._state = message.value self.schedule_update_ha_state() diff --git a/homeassistant/components/binary_sensor/android_ip_webcam.py b/homeassistant/components/binary_sensor/android_ip_webcam.py index 58de81c30..51055294d 100644 --- a/homeassistant/components/binary_sensor/android_ip_webcam.py +++ b/homeassistant/components/binary_sensor/android_ip_webcam.py @@ -8,14 +8,18 @@ import asyncio from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.android_ip_webcam import ( - KEY_MAP, DATA_IP_WEBCAM, AndroidIPCamEntity, CONF_HOST, CONF_NAME) + KEY_MAP, + DATA_IP_WEBCAM, + AndroidIPCamEntity, + CONF_HOST, + CONF_NAME, +) -DEPENDENCIES = ['android_ip_webcam'] +DEPENDENCIES = ["android_ip_webcam"] @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 IP Webcam binary sensors.""" if discovery_info is None: return @@ -24,8 +28,7 @@ def async_setup_platform(hass, config, async_add_entities, name = discovery_info[CONF_NAME] ipcam = hass.data[DATA_IP_WEBCAM][host] - async_add_entities( - [IPWebcamBinarySensor(name, host, ipcam, 'motion_active')], True) + async_add_entities([IPWebcamBinarySensor(name, host, ipcam, "motion_active")], True) class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice): @@ -37,7 +40,7 @@ class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice): self._sensor = sensor self._mapped_name = KEY_MAP.get(self._sensor, self._sensor) - self._name = '{} {}'.format(name, self._mapped_name) + self._name = "{} {}".format(name, self._mapped_name) self._state = None self._unit = None @@ -60,4 +63,4 @@ class IPWebcamBinarySensor(AndroidIPCamEntity, BinarySensorDevice): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return 'motion' + return "motion" diff --git a/homeassistant/components/binary_sensor/apcupsd.py b/homeassistant/components/binary_sensor/apcupsd.py index f876b8cc3..59ac35b9e 100644 --- a/homeassistant/components/binary_sensor/apcupsd.py +++ b/homeassistant/components/binary_sensor/apcupsd.py @@ -6,18 +6,17 @@ https://home-assistant.io/components/binary_sensor.apcupsd/ """ import voluptuous as vol -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv from homeassistant.components import apcupsd -DEFAULT_NAME = 'UPS Online Status' +DEFAULT_NAME = "UPS Online Status" DEPENDENCIES = [apcupsd.DOMAIN] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/binary_sensor/arest.py b/homeassistant/components/binary_sensor/arest.py index b70620df3..1595b8734 100644 --- a/homeassistant/components/binary_sensor/arest.py +++ b/homeassistant/components/binary_sensor/arest.py @@ -11,9 +11,11 @@ import requests import voluptuous as vol from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA) -from homeassistant.const import ( - CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_DEVICE_CLASS) + BinarySensorDevice, + PLATFORM_SCHEMA, + DEVICE_CLASSES_SCHEMA, +) +from homeassistant.const import CONF_RESOURCE, CONF_PIN, CONF_NAME, CONF_DEVICE_CLASS from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv @@ -21,12 +23,14 @@ _LOGGER = logging.getLogger(__name__) MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_RESOURCE): cv.url, - vol.Optional(CONF_NAME): cv.string, - vol.Required(CONF_PIN): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_RESOURCE): cv.url, + vol.Optional(CONF_NAME): cv.string, + vol.Required(CONF_PIN): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -38,8 +42,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: response = requests.get(resource, timeout=10).json() except requests.exceptions.MissingSchema: - _LOGGER.error("Missing resource or schema in configuration. " - "Add http:// to your URL") + _LOGGER.error( + "Missing resource or schema in configuration. " "Add http:// to your URL" + ) return False except requests.exceptions.ConnectionError: _LOGGER.error("No route to device at %s", resource) @@ -47,9 +52,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): arest = ArestData(resource, pin) - add_entities([ArestBinarySensor( - arest, resource, config.get(CONF_NAME, response[CONF_NAME]), - device_class, pin)], True) + add_entities( + [ + ArestBinarySensor( + arest, + resource, + config.get(CONF_NAME, response[CONF_NAME]), + device_class, + pin, + ) + ], + True, + ) class ArestBinarySensor(BinarySensorDevice): @@ -65,7 +79,8 @@ class ArestBinarySensor(BinarySensorDevice): if self._pin is not None: request = requests.get( - '{}/mode/{}/i'.format(self._resource, self._pin), timeout=10) + "{}/mode/{}/i".format(self._resource, self._pin), timeout=10 + ) if request.status_code != 200: _LOGGER.error("Can't set mode of %s", self._resource) @@ -77,7 +92,7 @@ class ArestBinarySensor(BinarySensorDevice): @property def is_on(self): """Return true if the binary sensor is on.""" - return bool(self.arest.data.get('state')) + return bool(self.arest.data.get("state")) @property def device_class(self): @@ -102,8 +117,9 @@ class ArestData: def update(self): """Get the latest data from aREST device.""" try: - response = requests.get('{}/digital/{}'.format( - self._resource, self._pin), timeout=10) - self.data = {'state': response.json()['return_value']} + response = requests.get( + "{}/digital/{}".format(self._resource, self._pin), timeout=10 + ) + self.data = {"state": response.json()["return_value"]} except requests.exceptions.ConnectionError: _LOGGER.error("No route to device '%s'", self._resource) diff --git a/homeassistant/components/binary_sensor/august.py b/homeassistant/components/binary_sensor/august.py index 7f5da3909..09d2dd89d 100644 --- a/homeassistant/components/binary_sensor/august.py +++ b/homeassistant/components/binary_sensor/august.py @@ -7,9 +7,9 @@ https://home-assistant.io/components/sensor.august/ from datetime import timedelta, datetime from homeassistant.components.august import DATA_AUGUST -from homeassistant.components.binary_sensor import (BinarySensorDevice) +from homeassistant.components.binary_sensor import BinarySensorDevice -DEPENDENCIES = ['august'] +DEPENDENCIES = ["august"] SCAN_INTERVAL = timedelta(seconds=5) @@ -22,21 +22,21 @@ def _retrieve_online_state(data, doorbell): def _retrieve_motion_state(data, doorbell): from august.activity import ActivityType - return _activity_time_based_state(data, doorbell, - [ActivityType.DOORBELL_MOTION, - ActivityType.DOORBELL_DING]) + + return _activity_time_based_state( + data, doorbell, [ActivityType.DOORBELL_MOTION, ActivityType.DOORBELL_DING] + ) def _retrieve_ding_state(data, doorbell): from august.activity import ActivityType - return _activity_time_based_state(data, doorbell, - [ActivityType.DOORBELL_DING]) + + return _activity_time_based_state(data, doorbell, [ActivityType.DOORBELL_DING]) def _activity_time_based_state(data, doorbell, activity_types): """Get the latest state of the sensor.""" - latest = data.get_latest_device_activity(doorbell.device_id, - *activity_types) + latest = data.get_latest_device_activity(doorbell.device_id, *activity_types) if latest is not None: start = latest.activity_start_time @@ -47,9 +47,9 @@ def _activity_time_based_state(data, doorbell, activity_types): # Sensor types: Name, device_class, state_provider SENSOR_TYPES = { - 'doorbell_ding': ['Ding', 'occupancy', _retrieve_ding_state], - 'doorbell_motion': ['Motion', 'motion', _retrieve_motion_state], - 'doorbell_online': ['Online', 'connectivity', _retrieve_online_state], + "doorbell_ding": ["Ding", "occupancy", _retrieve_ding_state], + "doorbell_motion": ["Motion", "motion", _retrieve_motion_state], + "doorbell_online": ["Online", "connectivity", _retrieve_online_state], } @@ -88,8 +88,9 @@ class AugustBinarySensor(BinarySensorDevice): @property def name(self): """Return the name of the binary sensor.""" - return "{} {}".format(self._doorbell.device_name, - SENSOR_TYPES[self._sensor_type][0]) + return "{} {}".format( + self._doorbell.device_name, SENSOR_TYPES[self._sensor_type][0] + ) def update(self): """Get the latest state of the sensor.""" diff --git a/homeassistant/components/binary_sensor/aurora.py b/homeassistant/components/binary_sensor/aurora.py index 04b402722..87d42e636 100644 --- a/homeassistant/components/binary_sensor/aurora.py +++ b/homeassistant/components/binary_sensor/aurora.py @@ -11,20 +11,18 @@ from aiohttp.hdrs import USER_AGENT import requests import voluptuous as vol -from homeassistant.components.binary_sensor import ( - PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import CONF_NAME, ATTR_ATTRIBUTION import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -CONF_ATTRIBUTION = "Data provided by the National Oceanic and Atmospheric" \ - "Administration" -CONF_THRESHOLD = 'forecast_threshold' +CONF_ATTRIBUTION = "Data provided by the National Oceanic and Atmospheric" "Administration" +CONF_THRESHOLD = "forecast_threshold" -DEFAULT_DEVICE_CLASS = 'visible' -DEFAULT_NAME = 'Aurora Visibility' +DEFAULT_DEVICE_CLASS = "visible" +DEFAULT_NAME = "Aurora Visibility" DEFAULT_THRESHOLD = 75 HA_USER_AGENT = "Home Assistant Aurora Tracker v.0.1.0" @@ -33,10 +31,12 @@ MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=5) URL = "http://services.swpc.noaa.gov/text/aurora-nowcast-map.txt" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_THRESHOLD, default=DEFAULT_THRESHOLD): cv.positive_int, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_THRESHOLD, default=DEFAULT_THRESHOLD): cv.positive_int, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -49,12 +49,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): threshold = config.get(CONF_THRESHOLD) try: - aurora_data = AuroraData( - hass.config.latitude, hass.config.longitude, threshold) + aurora_data = AuroraData(hass.config.latitude, hass.config.longitude, threshold) aurora_data.update() except requests.exceptions.HTTPError as error: - _LOGGER.error( - "Connection to aurora forecast service failed: %s", error) + _LOGGER.error("Connection to aurora forecast service failed: %s", error) return False add_entities([AuroraSensor(aurora_data, name)], True) @@ -71,7 +69,7 @@ class AuroraSensor(BinarySensorDevice): @property def name(self): """Return the name of the sensor.""" - return '{}'.format(self._name) + return "{}".format(self._name) @property def is_on(self): @@ -89,8 +87,8 @@ class AuroraSensor(BinarySensorDevice): attrs = {} if self.aurora_data: - attrs['visibility_level'] = self.aurora_data.visibility_level - attrs['message'] = self.aurora_data.is_visible_text + attrs["visibility_level"] = self.aurora_data.visibility_level + attrs["message"] = self.aurora_data.is_visible_text attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION return attrs @@ -127,8 +125,7 @@ class AuroraData: self.is_visible_text = "nothing's out" except requests.exceptions.HTTPError as error: - _LOGGER.error( - "Connection to aurora forecast service failed: %s", error) + _LOGGER.error("Connection to aurora forecast service failed: %s", error) return False def get_aurora_forecast(self): @@ -141,9 +138,11 @@ class AuroraData: ] # Convert lat and long for data points in table - converted_latitude = round((self.latitude / 180) - * self.number_of_latitude_intervals) - converted_longitude = round((self.longitude / 360) - * self.number_of_longitude_intervals) + converted_latitude = round( + (self.latitude / 180) * self.number_of_latitude_intervals + ) + converted_longitude = round( + (self.longitude / 360) * self.number_of_longitude_intervals + ) return forecast_table[converted_latitude][converted_longitude] diff --git a/homeassistant/components/binary_sensor/axis.py b/homeassistant/components/binary_sensor/axis.py index b66a766ca..d5bff23c2 100644 --- a/homeassistant/components/binary_sensor/axis.py +++ b/homeassistant/components/binary_sensor/axis.py @@ -13,7 +13,7 @@ from homeassistant.const import CONF_TRIGGER_TIME from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.util.dt import utcnow -DEPENDENCIES = ['axis'] +DEPENDENCIES = ["axis"] _LOGGER = logging.getLogger(__name__) @@ -55,13 +55,14 @@ class AxisBinarySensor(AxisDeviceEvent, BinarySensorDevice): # Set timer to wait until updating the state def _delay_update(now): """Timer callback for sensor update.""" - _LOGGER.debug("%s called delayed (%s sec) update", - self._name, self._delay) + _LOGGER.debug( + "%s called delayed (%s sec) update", self._name, self._delay + ) self.schedule_update_ha_state() self._timer = None self._timer = track_point_in_utc_time( - self.hass, _delay_update, - utcnow() + timedelta(seconds=self._delay)) + self.hass, _delay_update, utcnow() + timedelta(seconds=self._delay) + ) else: self.schedule_update_ha_state() diff --git a/homeassistant/components/binary_sensor/bayesian.py b/homeassistant/components/binary_sensor/bayesian.py index 88669d67d..988b3d7e4 100644 --- a/homeassistant/components/binary_sensor/bayesian.py +++ b/homeassistant/components/binary_sensor/bayesian.py @@ -11,58 +11,73 @@ from collections import OrderedDict import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.const import ( - CONF_ABOVE, CONF_BELOW, CONF_DEVICE_CLASS, CONF_ENTITY_ID, CONF_NAME, - CONF_PLATFORM, CONF_STATE, STATE_UNKNOWN) + CONF_ABOVE, + CONF_BELOW, + CONF_DEVICE_CLASS, + CONF_ENTITY_ID, + CONF_NAME, + CONF_PLATFORM, + CONF_STATE, + STATE_UNKNOWN, +) from homeassistant.core import callback from homeassistant.helpers import condition from homeassistant.helpers.event import async_track_state_change _LOGGER = logging.getLogger(__name__) -ATTR_OBSERVATIONS = 'observations' -ATTR_PROBABILITY = 'probability' -ATTR_PROBABILITY_THRESHOLD = 'probability_threshold' +ATTR_OBSERVATIONS = "observations" +ATTR_PROBABILITY = "probability" +ATTR_PROBABILITY_THRESHOLD = "probability_threshold" -CONF_OBSERVATIONS = 'observations' -CONF_PRIOR = 'prior' -CONF_PROBABILITY_THRESHOLD = 'probability_threshold' -CONF_P_GIVEN_F = 'prob_given_false' -CONF_P_GIVEN_T = 'prob_given_true' -CONF_TO_STATE = 'to_state' +CONF_OBSERVATIONS = "observations" +CONF_PRIOR = "prior" +CONF_PROBABILITY_THRESHOLD = "probability_threshold" +CONF_P_GIVEN_F = "prob_given_false" +CONF_P_GIVEN_T = "prob_given_true" +CONF_TO_STATE = "to_state" DEFAULT_NAME = "Bayesian Binary Sensor" DEFAULT_PROBABILITY_THRESHOLD = 0.5 -NUMERIC_STATE_SCHEMA = vol.Schema({ - CONF_PLATFORM: 'numeric_state', - vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Optional(CONF_ABOVE): vol.Coerce(float), - vol.Optional(CONF_BELOW): vol.Coerce(float), - vol.Required(CONF_P_GIVEN_T): vol.Coerce(float), - vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float) -}, required=True) +NUMERIC_STATE_SCHEMA = vol.Schema( + { + CONF_PLATFORM: "numeric_state", + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Optional(CONF_ABOVE): vol.Coerce(float), + vol.Optional(CONF_BELOW): vol.Coerce(float), + vol.Required(CONF_P_GIVEN_T): vol.Coerce(float), + vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float), + }, + required=True, +) -STATE_SCHEMA = vol.Schema({ - CONF_PLATFORM: CONF_STATE, - vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Required(CONF_TO_STATE): cv.string, - vol.Required(CONF_P_GIVEN_T): vol.Coerce(float), - vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float) -}, required=True) +STATE_SCHEMA = vol.Schema( + { + CONF_PLATFORM: CONF_STATE, + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Required(CONF_TO_STATE): cv.string, + vol.Required(CONF_P_GIVEN_T): vol.Coerce(float), + vol.Optional(CONF_P_GIVEN_F): vol.Coerce(float), + }, + required=True, +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_DEVICE_CLASS): cv.string, - vol.Required(CONF_OBSERVATIONS): - vol.Schema(vol.All(cv.ensure_list, - [vol.Any(NUMERIC_STATE_SCHEMA, STATE_SCHEMA)])), - vol.Required(CONF_PRIOR): vol.Coerce(float), - vol.Optional(CONF_PROBABILITY_THRESHOLD, - default=DEFAULT_PROBABILITY_THRESHOLD): vol.Coerce(float), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_DEVICE_CLASS): cv.string, + vol.Required(CONF_OBSERVATIONS): vol.Schema( + vol.All(cv.ensure_list, [vol.Any(NUMERIC_STATE_SCHEMA, STATE_SCHEMA)]) + ), + vol.Required(CONF_PRIOR): vol.Coerce(float), + vol.Optional( + CONF_PROBABILITY_THRESHOLD, default=DEFAULT_PROBABILITY_THRESHOLD + ): vol.Coerce(float), + } +) def update_probability(prior, prob_true, prob_false): @@ -75,8 +90,7 @@ def update_probability(prior, prob_true, prob_false): @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 Bayesian Binary sensor.""" name = config.get(CONF_NAME) observations = config.get(CONF_OBSERVATIONS) @@ -84,17 +98,20 @@ def async_setup_platform(hass, config, async_add_entities, probability_threshold = config.get(CONF_PROBABILITY_THRESHOLD) device_class = config.get(CONF_DEVICE_CLASS) - async_add_entities([ - BayesianBinarySensor( - name, prior, observations, probability_threshold, device_class) - ], True) + async_add_entities( + [ + BayesianBinarySensor( + name, prior, observations, probability_threshold, device_class + ) + ], + True, + ) class BayesianBinarySensor(BinarySensorDevice): """Representation of a Bayesian sensor.""" - def __init__(self, name, prior, observations, probability_threshold, - device_class): + def __init__(self, name, prior, observations, probability_threshold, device_class): """Initialize the Bayesian sensor.""" self._name = name self._observations = observations @@ -106,25 +123,25 @@ class BayesianBinarySensor(BinarySensorDevice): self.current_obs = OrderedDict({}) - to_observe = set(obs['entity_id'] for obs in self._observations) + to_observe = set(obs["entity_id"] for obs in self._observations) self.entity_obs = dict.fromkeys(to_observe, []) for ind, obs in enumerate(self._observations): - obs['id'] = ind - self.entity_obs[obs['entity_id']].append(obs) + obs["id"] = ind + self.entity_obs[obs["entity_id"]].append(obs) self.watchers = { - 'numeric_state': self._process_numeric_state, - 'state': self._process_state + "numeric_state": self._process_numeric_state, + "state": self._process_state, } @asyncio.coroutine def async_added_to_hass(self): """Call when entity about to be added.""" + @callback - def async_threshold_sensor_state_listener(entity, old_state, - new_state): + def async_threshold_sensor_state_listener(entity, old_state, new_state): """Handle sensor state changes.""" if new_state.state == STATE_UNKNOWN: return @@ -132,34 +149,33 @@ class BayesianBinarySensor(BinarySensorDevice): entity_obs_list = self.entity_obs[entity] for entity_obs in entity_obs_list: - platform = entity_obs['platform'] + platform = entity_obs["platform"] self.watchers[platform](entity_obs) prior = self.prior for obs in self.current_obs.values(): - prior = update_probability( - prior, obs['prob_true'], obs['prob_false']) + prior = update_probability(prior, obs["prob_true"], obs["prob_false"]) self.probability = prior self.hass.async_add_job(self.async_update_ha_state, True) - entities = [obs['entity_id'] for obs in self._observations] + entities = [obs["entity_id"] for obs in self._observations] async_track_state_change( - self.hass, entities, async_threshold_sensor_state_listener) + self.hass, entities, async_threshold_sensor_state_listener + ) def _update_current_obs(self, entity_observation, should_trigger): """Update current observation.""" - obs_id = entity_observation['id'] + obs_id = entity_observation["id"] if should_trigger: - prob_true = entity_observation['prob_given_true'] - prob_false = entity_observation.get( - 'prob_given_false', 1 - prob_true) + prob_true = entity_observation["prob_given_true"] + prob_false = entity_observation.get("prob_given_false", 1 - prob_true) self.current_obs[obs_id] = { - 'prob_true': prob_true, - 'prob_false': prob_false + "prob_true": prob_true, + "prob_false": prob_false, } else: @@ -167,21 +183,26 @@ class BayesianBinarySensor(BinarySensorDevice): def _process_numeric_state(self, entity_observation): """Add entity to current_obs if numeric state conditions are met.""" - entity = entity_observation['entity_id'] + entity = entity_observation["entity_id"] should_trigger = condition.async_numeric_state( - self.hass, entity, - entity_observation.get('below'), - entity_observation.get('above'), None, entity_observation) + self.hass, + entity, + entity_observation.get("below"), + entity_observation.get("above"), + None, + entity_observation, + ) self._update_current_obs(entity_observation, should_trigger) def _process_state(self, entity_observation): """Add entity to current observations if state conditions are met.""" - entity = entity_observation['entity_id'] + entity = entity_observation["entity_id"] should_trigger = condition.state( - self.hass, entity, entity_observation.get('to_state')) + self.hass, entity, entity_observation.get("to_state") + ) self._update_current_obs(entity_observation, should_trigger) diff --git a/homeassistant/components/binary_sensor/bbb_gpio.py b/homeassistant/components/binary_sensor/bbb_gpio.py index 8968b6803..db39bd24c 100644 --- a/homeassistant/components/binary_sensor/bbb_gpio.py +++ b/homeassistant/components/binary_sensor/bbb_gpio.py @@ -9,36 +9,35 @@ import logging import voluptuous as vol from homeassistant.components import bbb_gpio -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) -from homeassistant.const import (DEVICE_DEFAULT_NAME, CONF_NAME) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA +from homeassistant.const import DEVICE_DEFAULT_NAME, CONF_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['bbb_gpio'] +DEPENDENCIES = ["bbb_gpio"] -CONF_PINS = 'pins' -CONF_BOUNCETIME = 'bouncetime' -CONF_INVERT_LOGIC = 'invert_logic' -CONF_PULL_MODE = 'pull_mode' +CONF_PINS = "pins" +CONF_BOUNCETIME = "bouncetime" +CONF_INVERT_LOGIC = "invert_logic" +CONF_PULL_MODE = "pull_mode" DEFAULT_BOUNCETIME = 50 DEFAULT_INVERT_LOGIC = False -DEFAULT_PULL_MODE = 'UP' +DEFAULT_PULL_MODE = "UP" -PIN_SCHEMA = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_BOUNCETIME, default=DEFAULT_BOUNCETIME): cv.positive_int, - vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, - vol.Optional(CONF_PULL_MODE, default=DEFAULT_PULL_MODE): - vol.In(['UP', 'DOWN']) -}) +PIN_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_BOUNCETIME, default=DEFAULT_BOUNCETIME): cv.positive_int, + vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, + vol.Optional(CONF_PULL_MODE, default=DEFAULT_PULL_MODE): vol.In(["UP", "DOWN"]), + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PINS, default={}): - vol.Schema({cv.string: PIN_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_PINS, default={}): vol.Schema({cv.string: PIN_SCHEMA})} +) def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/binary_sensor/blink.py b/homeassistant/components/binary_sensor/blink.py index 6ade20b72..5d4ff8acb 100644 --- a/homeassistant/components/binary_sensor/blink.py +++ b/homeassistant/components/binary_sensor/blink.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.blink/ from homeassistant.components.blink import DOMAIN from homeassistant.components.binary_sensor import BinarySensorDevice -DEPENDENCIES = ['blink'] +DEPENDENCIES = ["blink"] def setup_platform(hass, config, add_entities, discovery_info=None): @@ -28,7 +28,7 @@ class BlinkCameraMotionSensor(BinarySensorDevice): def __init__(self, name, data): """Initialize the sensor.""" - self._name = 'blink_' + name + '_motion_enabled' + self._name = "blink_" + name + "_motion_enabled" self._camera_name = name self.data = data self._state = self.data.cameras[self._camera_name].armed @@ -54,7 +54,7 @@ class BlinkSystemSensor(BinarySensorDevice): def __init__(self, data): """Initialize the sensor.""" - self._name = 'blink armed status' + self._name = "blink armed status" self.data = data self._state = self.data.arm diff --git a/homeassistant/components/binary_sensor/bloomsky.py b/homeassistant/components/binary_sensor/bloomsky.py index ecffb3acc..8a084b59a 100644 --- a/homeassistant/components/binary_sensor/bloomsky.py +++ b/homeassistant/components/binary_sensor/bloomsky.py @@ -8,24 +8,23 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['bloomsky'] +DEPENDENCIES = ["bloomsky"] -SENSOR_TYPES = { - 'Rain': 'moisture', - 'Night': None, -} +SENSOR_TYPES = {"Rain": "moisture", "Night": None} -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ) + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -36,8 +35,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for device in bloomsky.BLOOMSKY.devices.values(): for variable in sensors: - add_entities( - [BloomSkySensor(bloomsky.BLOOMSKY, device, variable)], True) + add_entities([BloomSkySensor(bloomsky.BLOOMSKY, device, variable)], True) class BloomSkySensor(BinarySensorDevice): @@ -46,9 +44,9 @@ class BloomSkySensor(BinarySensorDevice): def __init__(self, bs, device, sensor_name): """Initialize a BloomSky binary sensor.""" self._bloomsky = bs - self._device_id = device['DeviceID'] + self._device_id = device["DeviceID"] self._sensor_name = sensor_name - self._name = '{} {}'.format(device['DeviceName'], sensor_name) + self._name = "{} {}".format(device["DeviceName"], sensor_name) self._state = None @property @@ -70,5 +68,4 @@ class BloomSkySensor(BinarySensorDevice): """Request an update from the BloomSky API.""" self._bloomsky.refresh_devices() - self._state = \ - self._bloomsky.devices[self._device_id]['Data'][self._sensor_name] + self._state = self._bloomsky.devices[self._device_id]["Data"][self._sensor_name] diff --git a/homeassistant/components/binary_sensor/bmw_connected_drive.py b/homeassistant/components/binary_sensor/bmw_connected_drive.py index 36229828d..93655875e 100644 --- a/homeassistant/components/binary_sensor/bmw_connected_drive.py +++ b/homeassistant/components/binary_sensor/bmw_connected_drive.py @@ -10,22 +10,22 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN -DEPENDENCIES = ['bmw_connected_drive'] +DEPENDENCIES = ["bmw_connected_drive"] _LOGGER = logging.getLogger(__name__) SENSOR_TYPES = { - 'lids': ['Doors', 'opening'], - 'windows': ['Windows', 'opening'], - 'door_lock_state': ['Door lock state', 'safety'], - 'lights_parking': ['Parking lights', 'light'], - 'condition_based_services': ['Condition based services', 'problem'], - 'check_control_messages': ['Control messages', 'problem'] + "lids": ["Doors", "opening"], + "windows": ["Windows", "opening"], + "door_lock_state": ["Door lock state", "safety"], + "lights_parking": ["Parking lights", "light"], + "condition_based_services": ["Condition based services", "problem"], + "check_control_messages": ["Control messages", "problem"], } SENSOR_TYPES_ELEC = { - 'charging_status': ['Charging status', 'power'], - 'connection_status': ['Connection status', 'plug'] + "charging_status": ["Charging status", "power"], + "connection_status": ["Connection status", "plug"], } SENSOR_TYPES_ELEC.update(SENSOR_TYPES) @@ -34,22 +34,23 @@ SENSOR_TYPES_ELEC.update(SENSOR_TYPES) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the BMW sensors.""" accounts = hass.data[BMW_DOMAIN] - _LOGGER.debug('Found BMW accounts: %s', - ', '.join([a.name for a in accounts])) + _LOGGER.debug("Found BMW accounts: %s", ", ".join([a.name for a in accounts])) devices = [] for account in accounts: for vehicle in account.account.vehicles: if vehicle.has_hv_battery: - _LOGGER.debug('BMW with a high voltage battery') + _LOGGER.debug("BMW with a high voltage battery") for key, value in sorted(SENSOR_TYPES_ELEC.items()): - device = BMWConnectedDriveSensor(account, vehicle, key, - value[0], value[1]) + device = BMWConnectedDriveSensor( + account, vehicle, key, value[0], value[1] + ) devices.append(device) elif vehicle.has_internal_combustion_engine: - _LOGGER.debug('BMW with an internal combustion engine') + _LOGGER.debug("BMW with an internal combustion engine") for key, value in sorted(SENSOR_TYPES.items()): - device = BMWConnectedDriveSensor(account, vehicle, key, - value[0], value[1]) + device = BMWConnectedDriveSensor( + account, vehicle, key, value[0], value[1] + ) devices.append(device) add_entities(devices, True) @@ -57,14 +58,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class BMWConnectedDriveSensor(BinarySensorDevice): """Representation of a BMW vehicle binary sensor.""" - def __init__(self, account, vehicle, attribute: str, sensor_name, - device_class): + def __init__(self, account, vehicle, attribute: str, sensor_name, device_class): """Constructor.""" self._account = account self._vehicle = vehicle self._attribute = attribute - self._name = '{} {}'.format(self._vehicle.name, self._attribute) - self._unique_id = '{}-{}'.format(self._vehicle.vin, self._attribute) + self._name = "{} {}".format(self._vehicle.name, self._attribute) + self._unique_id = "{}-{}".format(self._vehicle.vin, self._attribute) self._sensor_name = sensor_name self._device_class = device_class self._state = None @@ -101,39 +101,37 @@ class BMWConnectedDriveSensor(BinarySensorDevice): def device_state_attributes(self): """Return the state attributes of the binary sensor.""" vehicle_state = self._vehicle.state - result = { - 'car': self._vehicle.name - } + result = {"car": self._vehicle.name} - if self._attribute == 'lids': + if self._attribute == "lids": for lid in vehicle_state.lids: result[lid.name] = lid.state.value - elif self._attribute == 'windows': + elif self._attribute == "windows": for window in vehicle_state.windows: result[window.name] = window.state.value - elif self._attribute == 'door_lock_state': - result['door_lock_state'] = vehicle_state.door_lock_state.value - result['last_update_reason'] = vehicle_state.last_update_reason - elif self._attribute == 'lights_parking': - result['lights_parking'] = vehicle_state.parking_lights.value - elif self._attribute == 'condition_based_services': + elif self._attribute == "door_lock_state": + result["door_lock_state"] = vehicle_state.door_lock_state.value + result["last_update_reason"] = vehicle_state.last_update_reason + elif self._attribute == "lights_parking": + result["lights_parking"] = vehicle_state.parking_lights.value + elif self._attribute == "condition_based_services": for report in vehicle_state.condition_based_services: result.update(self._format_cbs_report(report)) - elif self._attribute == 'check_control_messages': + elif self._attribute == "check_control_messages": check_control_messages = vehicle_state.check_control_messages if not check_control_messages: - result['check_control_messages'] = 'OK' + result["check_control_messages"] = "OK" else: - result['check_control_messages'] = check_control_messages - elif self._attribute == 'charging_status': - result['charging_status'] = vehicle_state.charging_status.value + result["check_control_messages"] = check_control_messages + elif self._attribute == "charging_status": + result["charging_status"] = vehicle_state.charging_status.value # pylint: disable=protected-access - result['last_charging_end_result'] = \ - vehicle_state._attributes['lastChargingEndResult'] - if self._attribute == 'connection_status': + result["last_charging_end_result"] = vehicle_state._attributes[ + "lastChargingEndResult" + ] + if self._attribute == "connection_status": # pylint: disable=protected-access - result['connection_status'] = \ - vehicle_state._attributes['connectionStatus'] + result["connection_status"] = vehicle_state._attributes["connectionStatus"] return sorted(result.items()) @@ -141,49 +139,52 @@ class BMWConnectedDriveSensor(BinarySensorDevice): """Read new state data from the library.""" from bimmer_connected.state import LockState from bimmer_connected.state import ChargingState + vehicle_state = self._vehicle.state # device class opening: On means open, Off means closed - if self._attribute == 'lids': + if self._attribute == "lids": _LOGGER.debug("Status of lid: %s", vehicle_state.all_lids_closed) self._state = not vehicle_state.all_lids_closed - if self._attribute == 'windows': + if self._attribute == "windows": self._state = not vehicle_state.all_windows_closed # device class safety: On means unsafe, Off means safe - if self._attribute == 'door_lock_state': + if self._attribute == "door_lock_state": # Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED - self._state = vehicle_state.door_lock_state not in \ - [LockState.LOCKED, LockState.SECURED] + self._state = vehicle_state.door_lock_state not in [ + LockState.LOCKED, + LockState.SECURED, + ] # device class light: On means light detected, Off means no light - if self._attribute == 'lights_parking': + if self._attribute == "lights_parking": self._state = vehicle_state.are_parking_lights_on # device class problem: On means problem detected, Off means no problem - if self._attribute == 'condition_based_services': + if self._attribute == "condition_based_services": self._state = not vehicle_state.are_all_cbs_ok - if self._attribute == 'check_control_messages': + if self._attribute == "check_control_messages": self._state = vehicle_state.has_check_control_messages # device class power: On means power detected, Off means no power - if self._attribute == 'charging_status': - self._state = vehicle_state.charging_status in \ - [ChargingState.CHARGING] + if self._attribute == "charging_status": + self._state = vehicle_state.charging_status in [ChargingState.CHARGING] # device class plug: On means device is plugged in, # Off means device is unplugged - if self._attribute == 'connection_status': + if self._attribute == "connection_status": # pylint: disable=protected-access - self._state = (vehicle_state._attributes['connectionStatus'] == - 'CONNECTED') + self._state = vehicle_state._attributes["connectionStatus"] == "CONNECTED" @staticmethod def _format_cbs_report(report): result = {} - service_type = report.service_type.lower().replace('_', ' ') - result['{} status'.format(service_type)] = report.state.value + service_type = report.service_type.lower().replace("_", " ") + result["{} status".format(service_type)] = report.state.value if report.due_date is not None: - result['{} date'.format(service_type)] = \ - report.due_date.strftime('%Y-%m-%d') + result["{} date".format(service_type)] = report.due_date.strftime( + "%Y-%m-%d" + ) if report.due_distance is not None: - result['{} distance'.format(service_type)] = \ - '{} km'.format(report.due_distance) + result["{} distance".format(service_type)] = "{} km".format( + report.due_distance + ) return result def update_callback(self): diff --git a/homeassistant/components/binary_sensor/command_line.py b/homeassistant/components/binary_sensor/command_line.py index a3f159578..4927481a3 100644 --- a/homeassistant/components/binary_sensor/command_line.py +++ b/homeassistant/components/binary_sensor/command_line.py @@ -11,33 +11,42 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import ( - BinarySensorDevice, DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA) + BinarySensorDevice, + DEVICE_CLASSES_SCHEMA, + PLATFORM_SCHEMA, +) from homeassistant.components.sensor.command_line import CommandSensorData from homeassistant.const import ( - CONF_PAYLOAD_OFF, CONF_PAYLOAD_ON, CONF_NAME, CONF_VALUE_TEMPLATE, - CONF_COMMAND, CONF_DEVICE_CLASS) + CONF_PAYLOAD_OFF, + CONF_PAYLOAD_ON, + CONF_NAME, + CONF_VALUE_TEMPLATE, + CONF_COMMAND, + CONF_DEVICE_CLASS, +) _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Binary Command Sensor' -DEFAULT_PAYLOAD_ON = 'ON' -DEFAULT_PAYLOAD_OFF = 'OFF' +DEFAULT_NAME = "Binary Command Sensor" +DEFAULT_PAYLOAD_ON = "ON" +DEFAULT_PAYLOAD_OFF = "OFF" SCAN_INTERVAL = timedelta(seconds=60) -CONF_COMMAND_TIMEOUT = 'command_timeout' +CONF_COMMAND_TIMEOUT = "command_timeout" DEFAULT_TIMEOUT = 15 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COMMAND): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, - vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional( - CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_COMMAND): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_COMMAND_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -53,16 +62,22 @@ def setup_platform(hass, config, add_entities, discovery_info=None): value_template.hass = hass data = CommandSensorData(hass, command, command_timeout) - add_entities([CommandBinarySensor( - hass, data, name, device_class, payload_on, payload_off, - value_template)], True) + add_entities( + [ + CommandBinarySensor( + hass, data, name, device_class, payload_on, payload_off, value_template + ) + ], + True, + ) class CommandBinarySensor(BinarySensorDevice): """Representation of a command line binary sensor.""" - def __init__(self, hass, data, name, device_class, payload_on, - payload_off, value_template): + def __init__( + self, hass, data, name, device_class, payload_on, payload_off, value_template + ): """Initialize the Command line binary sensor.""" self._hass = hass self.data = data @@ -83,7 +98,7 @@ class CommandBinarySensor(BinarySensorDevice): """Return true if the binary sensor is on.""" return self._state - @ property + @property def device_class(self): """Return the class of the binary sensor.""" return self._device_class @@ -94,8 +109,7 @@ class CommandBinarySensor(BinarySensorDevice): value = self.data.value if self._value_template is not None: - value = self._value_template.render_with_possible_json_value( - value, False) + value = self._value_template.render_with_possible_json_value(value, False) if value == self._payload_on: self._state = True elif value == self._payload_off: diff --git a/homeassistant/components/binary_sensor/concord232.py b/homeassistant/components/binary_sensor/concord232.py index 26f35d603..ad27e249f 100644 --- a/homeassistant/components/binary_sensor/concord232.py +++ b/homeassistant/components/binary_sensor/concord232.py @@ -11,35 +11,39 @@ import requests import voluptuous as vol from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES) -from homeassistant.const import (CONF_HOST, CONF_PORT) + BinarySensorDevice, + PLATFORM_SCHEMA, + DEVICE_CLASSES, +) +from homeassistant.const import CONF_HOST, CONF_PORT import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['concord232==0.15'] +REQUIREMENTS = ["concord232==0.15"] _LOGGER = logging.getLogger(__name__) -CONF_EXCLUDE_ZONES = 'exclude_zones' -CONF_ZONE_TYPES = 'zone_types' +CONF_EXCLUDE_ZONES = "exclude_zones" +CONF_ZONE_TYPES = "zone_types" -DEFAULT_HOST = 'localhost' -DEFAULT_NAME = 'Alarm' -DEFAULT_PORT = '5007' +DEFAULT_HOST = "localhost" +DEFAULT_NAME = "Alarm" +DEFAULT_PORT = "5007" DEFAULT_SSL = False SCAN_INTERVAL = datetime.timedelta(seconds=10) -ZONE_TYPES_SCHEMA = vol.Schema({ - cv.positive_int: vol.In(DEVICE_CLASSES), -}) +ZONE_TYPES_SCHEMA = vol.Schema({cv.positive_int: vol.In(DEVICE_CLASSES)}) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_EXCLUDE_ZONES, default=[]): - vol.All(cv.ensure_list, [cv.positive_int]), - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_EXCLUDE_ZONES, default=[]): vol.All( + cv.ensure_list, [cv.positive_int] + ), + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -54,7 +58,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: _LOGGER.debug("Initializing client") - client = concord232_client.Client('http://{}:{}'.format(host, port)) + client = concord232_client.Client("http://{}:{}".format(host, port)) client.zones = client.list_zones() client.last_zone_update = datetime.datetime.now() @@ -67,15 +71,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # name mapping to different sensors in an unpredictable way. Sort # the zones by zone number to prevent this. - client.zones.sort(key=lambda zone: zone['number']) + client.zones.sort(key=lambda zone: zone["number"]) for zone in client.zones: - _LOGGER.info("Loading Zone found: %s", zone['name']) - if zone['number'] not in exclude: + _LOGGER.info("Loading Zone found: %s", zone["name"]) + if zone["number"] not in exclude: sensors.append( Concord232ZoneSensor( - hass, client, zone, zone_types.get( - zone['number'], get_opening_type(zone)) + hass, + client, + zone, + zone_types.get(zone["number"], get_opening_type(zone)), ) ) @@ -84,15 +90,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): def get_opening_type(zone): """Return the result of the type guessing from name.""" - if 'MOTION' in zone['name']: - return 'motion' - if 'KEY' in zone['name']: - return 'safety' - if 'SMOKE' in zone['name']: - return 'smoke' - if 'WATER' in zone['name']: - return 'water' - return 'opening' + if "MOTION" in zone["name"]: + return "motion" + if "KEY" in zone["name"]: + return "safety" + if "SMOKE" in zone["name"]: + return "smoke" + if "WATER" in zone["name"]: + return "water" + return "opening" class Concord232ZoneSensor(BinarySensorDevice): @@ -103,7 +109,7 @@ class Concord232ZoneSensor(BinarySensorDevice): self._hass = hass self._client = client self._zone = zone - self._number = zone['number'] + self._number = zone["number"] self._zone_type = zone_type @property @@ -119,13 +125,13 @@ class Concord232ZoneSensor(BinarySensorDevice): @property def name(self): """Return the name of the binary sensor.""" - return self._zone['name'] + return self._zone["name"] @property def is_on(self): """Return true if the binary sensor is on.""" # True means "faulted" or "open" or "abnormal state" - return bool(self._zone['state'] != 'Normal') + return bool(self._zone["state"] != "Normal") def update(self): """Get updated stats from API.""" @@ -134,8 +140,9 @@ class Concord232ZoneSensor(BinarySensorDevice): if last_update > datetime.timedelta(seconds=1): self._client.zones = self._client.list_zones() self._client.last_zone_update = datetime.datetime.now() - _LOGGER.debug("Updated from zone: %s", self._zone['name']) + _LOGGER.debug("Updated from zone: %s", self._zone["name"]) - if hasattr(self._client, 'zones'): - self._zone = next((x for x in self._client.zones - if x['number'] == self._number), None) + if hasattr(self._client, "zones"): + self._zone = next( + (x for x in self._client.zones if x["number"] == self._number), None + ) diff --git a/homeassistant/components/binary_sensor/deconz.py b/homeassistant/components/binary_sensor/deconz.py index d2ca9e7c5..dffb52d6a 100644 --- a/homeassistant/components/binary_sensor/deconz.py +++ b/homeassistant/components/binary_sensor/deconz.py @@ -6,38 +6,47 @@ https://home-assistant.io/components/binary_sensor.deconz/ """ from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.deconz.const import ( - ATTR_DARK, ATTR_ON, CONF_ALLOW_CLIP_SENSOR, DOMAIN as DATA_DECONZ, - DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DECONZ_DOMAIN) + ATTR_DARK, + ATTR_ON, + CONF_ALLOW_CLIP_SENSOR, + DOMAIN as DATA_DECONZ, + DATA_DECONZ_ID, + DATA_DECONZ_UNSUB, + DECONZ_DOMAIN, +) from homeassistant.const import ATTR_BATTERY_LEVEL from homeassistant.core import callback from homeassistant.helpers.device_registry import CONNECTION_ZIGBEE from homeassistant.helpers.dispatcher import async_dispatcher_connect -DEPENDENCIES = ['deconz'] +DEPENDENCIES = ["deconz"] -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): """Old way of setting up deCONZ binary sensors.""" pass async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the deCONZ binary sensor.""" + @callback def async_add_sensor(sensors): """Add binary sensor from deCONZ.""" from pydeconz.sensor import DECONZ_BINARY_SENSOR + entities = [] allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True) for sensor in sensors: - if sensor.type in DECONZ_BINARY_SENSOR and \ - not (not allow_clip_sensor and sensor.type.startswith('CLIP')): + if sensor.type in DECONZ_BINARY_SENSOR and not ( + not allow_clip_sensor and sensor.type.startswith("CLIP") + ): entities.append(DeconzBinarySensor(sensor)) async_add_entities(entities, True) hass.data[DATA_DECONZ_UNSUB].append( - async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_sensor)) + async_dispatcher_connect(hass, "deconz_new_sensor", async_add_sensor) + ) async_add_sensor(hass.data[DATA_DECONZ].sensors.values()) @@ -66,10 +75,12 @@ class DeconzBinarySensor(BinarySensorDevice): If reason is that state is updated, or reachable has changed or battery has changed. """ - if reason['state'] or \ - 'reachable' in reason['attr'] or \ - 'battery' in reason['attr'] or \ - 'on' in reason['attr']: + if ( + reason["state"] + or "reachable" in reason["attr"] + or "battery" in reason["attr"] + or "on" in reason["attr"] + ): self.async_schedule_update_ha_state() @property @@ -111,6 +122,7 @@ class DeconzBinarySensor(BinarySensorDevice): def device_state_attributes(self): """Return the state attributes of the sensor.""" from pydeconz.sensor import PRESENCE + attr = {} if self._sensor.battery: attr[ATTR_BATTERY_LEVEL] = self._sensor.battery @@ -123,15 +135,14 @@ class DeconzBinarySensor(BinarySensorDevice): @property def device_info(self): """Return a device description for device registry.""" - if (self._sensor.uniqueid is None or - self._sensor.uniqueid.count(':') != 7): + if self._sensor.uniqueid is None or self._sensor.uniqueid.count(":") != 7: return None - serial = self._sensor.uniqueid.split('-', 1)[0] + serial = self._sensor.uniqueid.split("-", 1)[0] return { - 'connections': {(CONNECTION_ZIGBEE, serial)}, - 'identifiers': {(DECONZ_DOMAIN, serial)}, - 'manufacturer': self._sensor.manufacturer, - 'model': self._sensor.modelid, - 'name': self._sensor.name, - 'sw_version': self._sensor.swversion, + "connections": {(CONNECTION_ZIGBEE, serial)}, + "identifiers": {(DECONZ_DOMAIN, serial)}, + "manufacturer": self._sensor.manufacturer, + "model": self._sensor.modelid, + "name": self._sensor.name, + "sw_version": self._sensor.swversion, } diff --git a/homeassistant/components/binary_sensor/demo.py b/homeassistant/components/binary_sensor/demo.py index d656b79e8..c80891dcf 100644 --- a/homeassistant/components/binary_sensor/demo.py +++ b/homeassistant/components/binary_sensor/demo.py @@ -9,10 +9,12 @@ from homeassistant.components.binary_sensor import BinarySensorDevice def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo binary sensor platform.""" - add_entities([ - DemoBinarySensor('Basement Floor Wet', False, 'moisture'), - DemoBinarySensor('Movement Backyard', True, 'motion'), - ]) + add_entities( + [ + DemoBinarySensor("Basement Floor Wet", False, "moisture"), + DemoBinarySensor("Movement Backyard", True, "motion"), + ] + ) class DemoBinarySensor(BinarySensorDevice): diff --git a/homeassistant/components/binary_sensor/digital_ocean.py b/homeassistant/components/binary_sensor/digital_ocean.py index 0f604c525..bc4ef01db 100644 --- a/homeassistant/components/binary_sensor/digital_ocean.py +++ b/homeassistant/components/binary_sensor/digital_ocean.py @@ -9,23 +9,32 @@ import logging import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.components.digital_ocean import ( - CONF_DROPLETS, ATTR_CREATED_AT, ATTR_DROPLET_ID, ATTR_DROPLET_NAME, - ATTR_FEATURES, ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, - ATTR_REGION, ATTR_VCPUS, CONF_ATTRIBUTION, DATA_DIGITAL_OCEAN) + CONF_DROPLETS, + ATTR_CREATED_AT, + ATTR_DROPLET_ID, + ATTR_DROPLET_NAME, + ATTR_FEATURES, + ATTR_IPV4_ADDRESS, + ATTR_IPV6_ADDRESS, + ATTR_MEMORY, + ATTR_REGION, + ATTR_VCPUS, + CONF_ATTRIBUTION, + DATA_DIGITAL_OCEAN, +) from homeassistant.const import ATTR_ATTRIBUTION _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Droplet' -DEFAULT_DEVICE_CLASS = 'moving' -DEPENDENCIES = ['digital_ocean'] +DEFAULT_NAME = "Droplet" +DEFAULT_DEVICE_CLASS = "moving" +DEPENDENCIES = ["digital_ocean"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DROPLETS): vol.All(cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_DROPLETS): vol.All(cv.ensure_list, [cv.string])} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -65,7 +74,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice): @property def is_on(self): """Return true if the binary sensor is on.""" - return self.data.status == 'active' + return self.data.status == "active" @property def device_class(self): @@ -84,7 +93,7 @@ class DigitalOceanBinarySensor(BinarySensorDevice): ATTR_IPV4_ADDRESS: self.data.ip_address, ATTR_IPV6_ADDRESS: self.data.ip_v6_address, ATTR_MEMORY: self.data.memory, - ATTR_REGION: self.data.region['name'], + ATTR_REGION: self.data.region["name"], ATTR_VCPUS: self.data.vcpus, } diff --git a/homeassistant/components/binary_sensor/ecobee.py b/homeassistant/components/binary_sensor/ecobee.py index 37f25476b..03ab9dafd 100644 --- a/homeassistant/components/binary_sensor/ecobee.py +++ b/homeassistant/components/binary_sensor/ecobee.py @@ -7,9 +7,9 @@ https://home-assistant.io/components/binary_sensor.ecobee/ from homeassistant.components import ecobee from homeassistant.components.binary_sensor import BinarySensorDevice -DEPENDENCIES = ['ecobee'] +DEPENDENCIES = ["ecobee"] -ECOBEE_CONFIG_FILE = 'ecobee.conf' +ECOBEE_CONFIG_FILE = "ecobee.conf" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -20,11 +20,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): dev = list() for index in range(len(data.ecobee.thermostats)): for sensor in data.ecobee.get_remote_sensors(index): - for item in sensor['capability']: - if item['type'] != 'occupancy': + for item in sensor["capability"]: + if item["type"] != "occupancy": continue - dev.append(EcobeeBinarySensor(sensor['name'], index)) + dev.append(EcobeeBinarySensor(sensor["name"], index)) add_entities(dev, True) @@ -34,11 +34,11 @@ class EcobeeBinarySensor(BinarySensorDevice): def __init__(self, sensor_name, sensor_index): """Initialize the sensor.""" - self._name = sensor_name + ' Occupancy' + self._name = sensor_name + " Occupancy" self.sensor_name = sensor_name self.index = sensor_index self._state = None - self._device_class = 'occupancy' + self._device_class = "occupancy" @property def name(self): @@ -48,7 +48,7 @@ class EcobeeBinarySensor(BinarySensorDevice): @property def is_on(self): """Return the status of the sensor.""" - return self._state == 'true' + return self._state == "true" @property def device_class(self): @@ -60,7 +60,6 @@ class EcobeeBinarySensor(BinarySensorDevice): data = ecobee.NETWORK data.update() for sensor in data.ecobee.get_remote_sensors(self.index): - for item in sensor['capability']: - if (item['type'] == 'occupancy' and - self.sensor_name == sensor['name']): - self._state = item['value'] + for item in sensor["capability"]: + if item["type"] == "occupancy" and self.sensor_name == sensor["name"]: + self._state = item["value"] diff --git a/homeassistant/components/binary_sensor/egardia.py b/homeassistant/components/binary_sensor/egardia.py index 0db2cac66..be4ca1ce2 100644 --- a/homeassistant/components/binary_sensor/egardia.py +++ b/homeassistant/components/binary_sensor/egardia.py @@ -9,21 +9,21 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.const import STATE_ON, STATE_OFF -from homeassistant.components.egardia import ( - EGARDIA_DEVICE, ATTR_DISCOVER_DEVICES) +from homeassistant.components.egardia import EGARDIA_DEVICE, ATTR_DISCOVER_DEVICES + _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['egardia'] -EGARDIA_TYPE_TO_DEVICE_CLASS = {'IR Sensor': 'motion', - 'Door Contact': 'opening', - 'IR': 'motion'} +DEPENDENCIES = ["egardia"] +EGARDIA_TYPE_TO_DEVICE_CLASS = { + "IR Sensor": "motion", + "Door Contact": "opening", + "IR": "motion", +} @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): """Initialize the platform.""" - if (discovery_info is None or - discovery_info[ATTR_DISCOVER_DEVICES] is None): + if discovery_info is None or discovery_info[ATTR_DISCOVER_DEVICES] is None: return disc_info = discovery_info[ATTR_DISCOVER_DEVICES] @@ -31,14 +31,17 @@ def async_setup_platform(hass, config, async_add_entities, async_add_entities( ( EgardiaBinarySensor( - sensor_id=disc_info[sensor]['id'], - name=disc_info[sensor]['name'], + sensor_id=disc_info[sensor]["id"], + name=disc_info[sensor]["name"], egardia_system=hass.data[EGARDIA_DEVICE], device_class=EGARDIA_TYPE_TO_DEVICE_CLASS.get( - disc_info[sensor]['type'], None) + disc_info[sensor]["type"], None + ), ) for sensor in disc_info - ), True) + ), + True, + ) class EgardiaBinarySensor(BinarySensorDevice): diff --git a/homeassistant/components/binary_sensor/eight_sleep.py b/homeassistant/components/binary_sensor/eight_sleep.py index 34d3a7a13..5a6fee370 100644 --- a/homeassistant/components/binary_sensor/eight_sleep.py +++ b/homeassistant/components/binary_sensor/eight_sleep.py @@ -8,20 +8,23 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.eight_sleep import ( - DATA_EIGHT, EightSleepHeatEntity, CONF_BINARY_SENSORS, NAME_MAP) + DATA_EIGHT, + EightSleepHeatEntity, + CONF_BINARY_SENSORS, + NAME_MAP, +) _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['eight_sleep'] +DEPENDENCIES = ["eight_sleep"] -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 eight sleep binary sensor.""" if discovery_info is None: return - name = 'Eight' + name = "Eight" sensors = discovery_info[CONF_BINARY_SENSORS] eight = hass.data[DATA_EIGHT] @@ -42,15 +45,19 @@ class EightHeatSensor(EightSleepHeatEntity, BinarySensorDevice): self._sensor = sensor self._mapped_name = NAME_MAP.get(self._sensor, self._sensor) - self._name = '{} {}'.format(name, self._mapped_name) + self._name = "{} {}".format(name, self._mapped_name) self._state = None - self._side = self._sensor.split('_')[0] + self._side = self._sensor.split("_")[0] self._userid = self._eight.fetch_userid(self._side) self._usrobj = self._eight.users[self._userid] - _LOGGER.debug("Presence Sensor: %s, Side: %s, User: %s", - self._sensor, self._side, self._userid) + _LOGGER.debug( + "Presence Sensor: %s, Side: %s, User: %s", + self._sensor, + self._side, + self._userid, + ) @property def name(self): diff --git a/homeassistant/components/binary_sensor/enocean.py b/homeassistant/components/binary_sensor/enocean.py index c883897c2..2545924e0 100644 --- a/homeassistant/components/binary_sensor/enocean.py +++ b/homeassistant/components/binary_sensor/enocean.py @@ -9,22 +9,26 @@ import logging import voluptuous as vol from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA) + BinarySensorDevice, + PLATFORM_SCHEMA, + DEVICE_CLASSES_SCHEMA, +) from homeassistant.components import enocean -from homeassistant.const import ( - CONF_NAME, CONF_ID, CONF_DEVICE_CLASS) +from homeassistant.const import CONF_NAME, CONF_ID, CONF_DEVICE_CLASS import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['enocean'] -DEFAULT_NAME = 'EnOcean binary sensor' +DEPENDENCIES = ["enocean"] +DEFAULT_NAME = "EnOcean binary sensor" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ID): vol.All(cv.ensure_list, [vol.Coerce(int)]), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -42,7 +46,7 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice): def __init__(self, dev_id, devname, device_class): """Initialize the EnOcean binary sensor.""" enocean.EnOceanDevice.__init__(self) - self.stype = 'listener' + self.stype = "listener" self.dev_id = dev_id self.which = -1 self.onoff = -1 @@ -84,7 +88,12 @@ class EnOceanBinarySensor(enocean.EnOceanDevice, BinarySensorDevice): elif value2 == 0x15: self.which = 10 self.onoff = 1 - self.hass.bus.fire('button_pressed', {'id': self.dev_id, - 'pushed': value, - 'which': self.which, - 'onoff': self.onoff}) + self.hass.bus.fire( + "button_pressed", + { + "id": self.dev_id, + "pushed": value, + "which": self.which, + "onoff": self.onoff, + }, + ) diff --git a/homeassistant/components/binary_sensor/envisalink.py b/homeassistant/components/binary_sensor/envisalink.py index 2568879bc..097bb388c 100644 --- a/homeassistant/components/binary_sensor/envisalink.py +++ b/homeassistant/components/binary_sensor/envisalink.py @@ -12,21 +12,25 @@ from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.envisalink import ( - DATA_EVL, ZONE_SCHEMA, CONF_ZONENAME, CONF_ZONETYPE, EnvisalinkDevice, - SIGNAL_ZONE_UPDATE) + DATA_EVL, + ZONE_SCHEMA, + CONF_ZONENAME, + CONF_ZONETYPE, + EnvisalinkDevice, + SIGNAL_ZONE_UPDATE, +) from homeassistant.const import ATTR_LAST_TRIP_TIME from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['envisalink'] +DEPENDENCIES = ["envisalink"] @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 Envisalink binary sensor devices.""" - configured_zones = discovery_info['zones'] + configured_zones = discovery_info["zones"] devices = [] for zone_num in configured_zones: @@ -36,8 +40,8 @@ def async_setup_platform(hass, config, async_add_entities, zone_num, device_config_data[CONF_ZONENAME], device_config_data[CONF_ZONETYPE], - hass.data[DATA_EVL].alarm_state['zone'][zone_num], - hass.data[DATA_EVL] + hass.data[DATA_EVL].alarm_state["zone"][zone_num], + hass.data[DATA_EVL], ) devices.append(device) @@ -47,20 +51,18 @@ def async_setup_platform(hass, config, async_add_entities, class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice): """Representation of an Envisalink binary sensor.""" - def __init__(self, hass, zone_number, zone_name, zone_type, info, - controller): + def __init__(self, hass, zone_number, zone_name, zone_type, info, controller): """Initialize the binary_sensor.""" self._zone_type = zone_type self._zone_number = zone_number - _LOGGER.debug('Setting up zone: %s', zone_name) + _LOGGER.debug("Setting up zone: %s", zone_name) super().__init__(zone_name, info, controller) @asyncio.coroutine def async_added_to_hass(self): """Register callbacks.""" - async_dispatcher_connect( - self.hass, SIGNAL_ZONE_UPDATE, self._update_callback) + async_dispatcher_connect(self.hass, SIGNAL_ZONE_UPDATE, self._update_callback) @property def device_state_attributes(self): @@ -76,7 +78,7 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice): # interval, so we subtract it from the current second-accurate time # unless it is already at the maximum value, in which case we set it # to None since we can't determine the actual value. - seconds_ago = self._info['last_fault'] + seconds_ago = self._info["last_fault"] if seconds_ago < 65536 * 5: now = dt_util.now().replace(microsecond=0) delta = datetime.timedelta(seconds=seconds_ago) @@ -90,7 +92,7 @@ class EnvisalinkBinarySensor(EnvisalinkDevice, BinarySensorDevice): @property def is_on(self): """Return true if sensor is on.""" - return self._info['status']['open'] + return self._info["status"]["open"] @property def device_class(self): diff --git a/homeassistant/components/binary_sensor/ffmpeg_motion.py b/homeassistant/components/binary_sensor/ffmpeg_motion.py index 365bcafbd..7a0f7eb76 100644 --- a/homeassistant/components/binary_sensor/ffmpeg_motion.py +++ b/homeassistant/components/binary_sensor/ffmpeg_motion.py @@ -11,44 +11,52 @@ import voluptuous as vol from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.components.ffmpeg import ( - FFmpegBase, DATA_FFMPEG, CONF_INPUT, CONF_EXTRA_ARGUMENTS, - CONF_INITIAL_STATE) + FFmpegBase, + DATA_FFMPEG, + CONF_INPUT, + CONF_EXTRA_ARGUMENTS, + CONF_INITIAL_STATE, +) from homeassistant.const import CONF_NAME -DEPENDENCIES = ['ffmpeg'] +DEPENDENCIES = ["ffmpeg"] _LOGGER = logging.getLogger(__name__) -CONF_RESET = 'reset' -CONF_CHANGES = 'changes' -CONF_REPEAT = 'repeat' -CONF_REPEAT_TIME = 'repeat_time' +CONF_RESET = "reset" +CONF_CHANGES = "changes" +CONF_REPEAT = "repeat" +CONF_REPEAT_TIME = "repeat_time" -DEFAULT_NAME = 'FFmpeg Motion' +DEFAULT_NAME = "FFmpeg Motion" DEFAULT_INIT_STATE = True -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_INPUT): cv.string, - vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, - vol.Optional(CONF_RESET, default=10): - vol.All(vol.Coerce(int), vol.Range(min=1)), - vol.Optional(CONF_CHANGES, default=10): - vol.All(vol.Coerce(float), vol.Range(min=0, max=99)), - vol.Inclusive(CONF_REPEAT, 'repeat'): - vol.All(vol.Coerce(int), vol.Range(min=1)), - vol.Inclusive(CONF_REPEAT_TIME, 'repeat'): - vol.All(vol.Coerce(int), vol.Range(min=1)), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_INPUT): cv.string, + vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, + vol.Optional(CONF_RESET, default=10): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + vol.Optional(CONF_CHANGES, default=10): vol.All( + vol.Coerce(float), vol.Range(min=0, max=99) + ), + vol.Inclusive(CONF_REPEAT, "repeat"): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + vol.Inclusive(CONF_REPEAT_TIME, "repeat"): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + } +) @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 FFmpeg binary motion sensor.""" manager = hass.data[DATA_FFMPEG] @@ -95,8 +103,7 @@ class FFmpegMotion(FFmpegBinarySensor): from haffmpeg import SensorMotion super().__init__(config) - self.ffmpeg = SensorMotion( - manager.binary, hass.loop, self._async_callback) + self.ffmpeg = SensorMotion(manager.binary, hass.loop, self._async_callback) @asyncio.coroutine def _async_start_ffmpeg(self, entity_ids): @@ -124,4 +131,4 @@ class FFmpegMotion(FFmpegBinarySensor): @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - return 'motion' + return "motion" diff --git a/homeassistant/components/binary_sensor/ffmpeg_noise.py b/homeassistant/components/binary_sensor/ffmpeg_noise.py index 73c84ac33..c6a480cdf 100644 --- a/homeassistant/components/binary_sensor/ffmpeg_noise.py +++ b/homeassistant/components/binary_sensor/ffmpeg_noise.py @@ -11,41 +11,47 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import PLATFORM_SCHEMA -from homeassistant.components.binary_sensor.ffmpeg_motion import ( - FFmpegBinarySensor) +from homeassistant.components.binary_sensor.ffmpeg_motion import FFmpegBinarySensor from homeassistant.components.ffmpeg import ( - DATA_FFMPEG, CONF_INPUT, CONF_OUTPUT, CONF_EXTRA_ARGUMENTS, - CONF_INITIAL_STATE) + DATA_FFMPEG, + CONF_INPUT, + CONF_OUTPUT, + CONF_EXTRA_ARGUMENTS, + CONF_INITIAL_STATE, +) from homeassistant.const import CONF_NAME -DEPENDENCIES = ['ffmpeg'] +DEPENDENCIES = ["ffmpeg"] _LOGGER = logging.getLogger(__name__) -CONF_PEAK = 'peak' -CONF_DURATION = 'duration' -CONF_RESET = 'reset' +CONF_PEAK = "peak" +CONF_DURATION = "duration" +CONF_RESET = "reset" -DEFAULT_NAME = 'FFmpeg Noise' +DEFAULT_NAME = "FFmpeg Noise" DEFAULT_INIT_STATE = True -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_INPUT): cv.string, - vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, - vol.Optional(CONF_OUTPUT): cv.string, - vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int), - vol.Optional(CONF_DURATION, default=1): - vol.All(vol.Coerce(int), vol.Range(min=1)), - vol.Optional(CONF_RESET, default=10): - vol.All(vol.Coerce(int), vol.Range(min=1)), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_INPUT): cv.string, + vol.Optional(CONF_INITIAL_STATE, default=DEFAULT_INIT_STATE): cv.boolean, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, + vol.Optional(CONF_OUTPUT): cv.string, + vol.Optional(CONF_PEAK, default=-30): vol.Coerce(int), + vol.Optional(CONF_DURATION, default=1): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + vol.Optional(CONF_RESET, default=10): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + } +) @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 FFmpeg noise binary sensor.""" manager = hass.data[DATA_FFMPEG] @@ -64,8 +70,7 @@ class FFmpegNoise(FFmpegBinarySensor): from haffmpeg import SensorNoise super().__init__(config) - self.ffmpeg = SensorNoise( - manager.binary, hass.loop, self._async_callback) + self.ffmpeg = SensorNoise(manager.binary, hass.loop, self._async_callback) @asyncio.coroutine def _async_start_ffmpeg(self, entity_ids): @@ -91,4 +96,4 @@ class FFmpegNoise(FFmpegBinarySensor): @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - return 'sound' + return "sound" diff --git a/homeassistant/components/binary_sensor/flic.py b/homeassistant/components/binary_sensor/flic.py index baf1d469b..1ddc5349c 100644 --- a/homeassistant/components/binary_sensor/flic.py +++ b/homeassistant/components/binary_sensor/flic.py @@ -11,41 +11,47 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_HOST, CONF_PORT, CONF_DISCOVERY, CONF_TIMEOUT, - EVENT_HOMEASSISTANT_STOP) -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) + CONF_HOST, + CONF_PORT, + CONF_DISCOVERY, + CONF_TIMEOUT, + EVENT_HOMEASSISTANT_STOP, +) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA -REQUIREMENTS = ['pyflic-homeassistant==0.4.dev0'] +REQUIREMENTS = ["pyflic-homeassistant==0.4.dev0"] _LOGGER = logging.getLogger(__name__) DEFAULT_TIMEOUT = 3 -CLICK_TYPE_SINGLE = 'single' -CLICK_TYPE_DOUBLE = 'double' -CLICK_TYPE_HOLD = 'hold' +CLICK_TYPE_SINGLE = "single" +CLICK_TYPE_DOUBLE = "double" +CLICK_TYPE_HOLD = "hold" CLICK_TYPES = [CLICK_TYPE_SINGLE, CLICK_TYPE_DOUBLE, CLICK_TYPE_HOLD] -CONF_IGNORED_CLICK_TYPES = 'ignored_click_types' +CONF_IGNORED_CLICK_TYPES = "ignored_click_types" -DEFAULT_HOST = 'localhost' +DEFAULT_HOST = "localhost" DEFAULT_PORT = 5551 -EVENT_NAME = 'flic_click' -EVENT_DATA_NAME = 'button_name' -EVENT_DATA_ADDRESS = 'button_address' -EVENT_DATA_TYPE = 'click_type' -EVENT_DATA_QUEUED_TIME = 'queued_time' +EVENT_NAME = "flic_click" +EVENT_DATA_NAME = "button_name" +EVENT_DATA_ADDRESS = "button_address" +EVENT_DATA_TYPE = "click_type" +EVENT_DATA_QUEUED_TIME = "queued_time" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_DISCOVERY, default=True): cv.boolean, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - vol.Optional(CONF_IGNORED_CLICK_TYPES): - vol.All(cv.ensure_list, [vol.In(CLICK_TYPES)]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_DISCOVERY, default=True): cv.boolean, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + vol.Optional(CONF_IGNORED_CLICK_TYPES): vol.All( + cv.ensure_list, [vol.In(CLICK_TYPES)] + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -72,15 +78,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if discovery: start_scanning(config, add_entities, client) - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, - lambda event: client.close()) + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, lambda event: client.close()) # Start the pyflic event handling thread threading.Thread(target=client.handle_events).start() def get_info_callback(items): """Add entities for already verified buttons.""" - addresses = items['bd_addr_of_verified_buttons'] or [] + addresses = items["bd_addr_of_verified_buttons"] or [] for address in addresses: setup_button(hass, config, add_entities, client, address) @@ -100,7 +105,8 @@ def start_scanning(config, add_entities, client): _LOGGER.info("Found new button %s", address) elif result != pyflic.ScanWizardResult.WizardFailedTimeout: _LOGGER.warning( - "Failed to connect to button %s. Reason: %s", address, result) + "Failed to connect to button %s. Reason: %s", address, result + ) # Restart scan wizard start_scanning(config, add_entities, client) @@ -167,7 +173,7 @@ class FlicButton(BinarySensorDevice): @property def name(self): """Return the name of the device.""" - return 'flic_{}'.format(self.address.replace(':', '')) + return "flic_{}".format(self.address.replace(":", "")) @property def address(self): @@ -187,21 +193,28 @@ class FlicButton(BinarySensorDevice): @property def device_state_attributes(self): """Return device specific state attributes.""" - return {'address': self.address} + return {"address": self.address} def _queued_event_check(self, click_type, time_diff): """Generate a log message and returns true if timeout exceeded.""" time_string = "{:d} {}".format( - time_diff, 'second' if time_diff == 1 else 'seconds') + time_diff, "second" if time_diff == 1 else "seconds" + ) if time_diff > self._timeout: _LOGGER.warning( "Queued %s dropped for %s. Time in queue was %s", - click_type, self.address, time_string) + click_type, + self.address, + time_string, + ) return True _LOGGER.info( "Queued %s allowed for %s. Time in queue was %s", - click_type, self.address, time_string) + click_type, + self.address, + time_string, + ) return False def _on_up_down(self, channel, click_type, was_queued, time_diff): @@ -225,18 +238,21 @@ class FlicButton(BinarySensorDevice): if hass_click_type in self._ignored_click_types: return - self._hass.bus.fire(EVENT_NAME, { - EVENT_DATA_NAME: self.name, - EVENT_DATA_ADDRESS: self.address, - EVENT_DATA_QUEUED_TIME: time_diff, - EVENT_DATA_TYPE: hass_click_type - }) + self._hass.bus.fire( + EVENT_NAME, + { + EVENT_DATA_NAME: self.name, + EVENT_DATA_ADDRESS: self.address, + EVENT_DATA_QUEUED_TIME: time_diff, + EVENT_DATA_TYPE: hass_click_type, + }, + ) - def _connection_status_changed( - self, channel, connection_status, disconnect_reason): + def _connection_status_changed(self, channel, connection_status, disconnect_reason): """Remove device, if button disconnects.""" import pyflic if connection_status == pyflic.ConnectionStatus.Disconnected: - _LOGGER.warning("Button (%s) disconnected. Reason: %s", - self.address, disconnect_reason) + _LOGGER.warning( + "Button (%s) disconnected. Reason: %s", self.address, disconnect_reason + ) diff --git a/homeassistant/components/binary_sensor/gc100.py b/homeassistant/components/binary_sensor/gc100.py index 27466f64c..8c257b0a9 100644 --- a/homeassistant/components/binary_sensor/gc100.py +++ b/homeassistant/components/binary_sensor/gc100.py @@ -7,20 +7,17 @@ https://home-assistant.io/components/binary_sensor.gc100/ import voluptuous as vol from homeassistant.components.gc100 import DATA_GC100, CONF_PORTS -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv -DEPENDENCIES = ['gc100'] +DEPENDENCIES = ["gc100"] -_SENSORS_SCHEMA = vol.Schema({ - cv.string: cv.string, -}) +_SENSORS_SCHEMA = vol.Schema({cv.string: cv.string}) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PORTS): vol.All(cv.ensure_list, [_SENSORS_SCHEMA]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_PORTS): vol.All(cv.ensure_list, [_SENSORS_SCHEMA])} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -29,8 +26,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): ports = config.get(CONF_PORTS) for port in ports: for port_addr, port_name in port.items(): - binary_sensors.append(GC100BinarySensor( - port_name, port_addr, hass.data[DATA_GC100])) + binary_sensors.append( + GC100BinarySensor(port_name, port_addr, hass.data[DATA_GC100]) + ) add_entities(binary_sensors, True) diff --git a/homeassistant/components/binary_sensor/hikvision.py b/homeassistant/components/binary_sensor/hikvision.py index 78e8f9a97..33a43dca5 100644 --- a/homeassistant/components/binary_sensor/hikvision.py +++ b/homeassistant/components/binary_sensor/hikvision.py @@ -10,64 +10,76 @@ import voluptuous as vol from homeassistant.helpers.event import track_point_in_utc_time from homeassistant.util.dt import utcnow -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_HOST, CONF_PORT, CONF_NAME, CONF_USERNAME, CONF_PASSWORD, - CONF_SSL, EVENT_HOMEASSISTANT_STOP, EVENT_HOMEASSISTANT_START, - ATTR_LAST_TRIP_TIME, CONF_CUSTOMIZE) + CONF_HOST, + CONF_PORT, + CONF_NAME, + CONF_USERNAME, + CONF_PASSWORD, + CONF_SSL, + EVENT_HOMEASSISTANT_STOP, + EVENT_HOMEASSISTANT_START, + ATTR_LAST_TRIP_TIME, + CONF_CUSTOMIZE, +) -REQUIREMENTS = ['pyhik==0.1.8'] +REQUIREMENTS = ["pyhik==0.1.8"] _LOGGER = logging.getLogger(__name__) -CONF_IGNORED = 'ignored' -CONF_DELAY = 'delay' +CONF_IGNORED = "ignored" +CONF_DELAY = "delay" DEFAULT_PORT = 80 DEFAULT_IGNORED = False DEFAULT_DELAY = 0 -ATTR_DELAY = 'delay' +ATTR_DELAY = "delay" DEVICE_CLASS_MAP = { - 'Motion': 'motion', - 'Line Crossing': 'motion', - 'Field Detection': 'motion', - 'Video Loss': None, - 'Tamper Detection': 'motion', - 'Shelter Alarm': None, - 'Disk Full': None, - 'Disk Error': None, - 'Net Interface Broken': 'connectivity', - 'IP Conflict': 'connectivity', - 'Illegal Access': None, - 'Video Mismatch': None, - 'Bad Video': None, - 'PIR Alarm': 'motion', - 'Face Detection': 'motion', - 'Scene Change Detection': 'motion', - 'I/O': None, - 'Unattended Baggage': 'motion', - 'Attended Baggage': 'motion', - 'Recording Failure': None, + "Motion": "motion", + "Line Crossing": "motion", + "Field Detection": "motion", + "Video Loss": None, + "Tamper Detection": "motion", + "Shelter Alarm": None, + "Disk Full": None, + "Disk Error": None, + "Net Interface Broken": "connectivity", + "IP Conflict": "connectivity", + "Illegal Access": None, + "Video Mismatch": None, + "Bad Video": None, + "PIR Alarm": "motion", + "Face Detection": "motion", + "Scene Change Detection": "motion", + "I/O": None, + "Unattended Baggage": "motion", + "Attended Baggage": "motion", + "Recording Failure": None, } -CUSTOMIZE_SCHEMA = vol.Schema({ - vol.Optional(CONF_IGNORED, default=DEFAULT_IGNORED): cv.boolean, - vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): cv.positive_int - }) +CUSTOMIZE_SCHEMA = vol.Schema( + { + vol.Optional(CONF_IGNORED, default=DEFAULT_IGNORED): cv.boolean, + vol.Optional(CONF_DELAY, default=DEFAULT_DELAY): cv.positive_int, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME): cv.string, - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_SSL, default=False): cv.boolean, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_CUSTOMIZE, default={}): - vol.Schema({cv.string: CUSTOMIZE_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_SSL, default=False): cv.boolean, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_CUSTOMIZE, default={}): vol.Schema( + {cv.string: CUSTOMIZE_SCHEMA} + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -81,11 +93,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): customize = config.get(CONF_CUSTOMIZE) if config.get(CONF_SSL): - protocol = 'https' + protocol = "https" else: - protocol = 'http' + protocol = "http" - url = '{}://{}'.format(protocol, host) + url = "{}://{}".format(protocol, host) data = HikvisionData(hass, url, port, name, username, password) @@ -98,21 +110,26 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for sensor, channel_list in data.sensors.items(): for channel in channel_list: # Build sensor name, then parse customize config. - if data.type == 'NVR': - sensor_name = '{}_{}'.format( - sensor.replace(' ', '_'), channel[1]) + if data.type == "NVR": + sensor_name = "{}_{}".format(sensor.replace(" ", "_"), channel[1]) else: - sensor_name = sensor.replace(' ', '_') + sensor_name = sensor.replace(" ", "_") custom = customize.get(sensor_name.lower(), {}) ignore = custom.get(CONF_IGNORED) delay = custom.get(CONF_DELAY) - _LOGGER.debug("Entity: %s - %s, Options - Ignore: %s, Delay: %s", - data.name, sensor_name, ignore, delay) + _LOGGER.debug( + "Entity: %s - %s, Options - Ignore: %s, Delay: %s", + data.name, + sensor_name, + ignore, + delay, + ) if not ignore: - entities.append(HikvisionBinarySensor( - hass, sensor, channel[1], data, delay)) + entities.append( + HikvisionBinarySensor(hass, sensor, channel[1], data, delay) + ) add_entities(entities) @@ -123,6 +140,7 @@ class HikvisionData: def __init__(self, hass, url, port, name, username, password): """Initialize the data object.""" from pyhik.hikvision import HikCamera + self._url = url self._port = port self._name = name @@ -130,8 +148,7 @@ class HikvisionData: self._password = password # Establish camera - self.camdata = HikCamera( - self._url, self._port, self._username, self._password) + self.camdata = HikCamera(self._url, self._port, self._username, self._password) if self._name is None: self._name = self.camdata.get_name @@ -182,12 +199,12 @@ class HikvisionBinarySensor(BinarySensorDevice): self._sensor = sensor self._channel = channel - if self._cam.type == 'NVR': - self._name = '{} {} {}'.format(self._cam.name, sensor, channel) + if self._cam.type == "NVR": + self._name = "{} {} {}".format(self._cam.name, sensor, channel) else: - self._name = '{} {}'.format(self._cam.name, sensor) + self._name = "{} {}".format(self._cam.name, sensor) - self._id = '{}.{}.{}'.format(self._cam.cam_id, sensor, channel) + self._id = "{}.{}.{}".format(self._cam.cam_id, sensor, channel) if delay is None: self._delay = 0 @@ -249,14 +266,15 @@ class HikvisionBinarySensor(BinarySensorDevice): def _update_callback(self, msg): """Update the sensor's state, if needed.""" - _LOGGER.debug('Callback signal from: %s', msg) + _LOGGER.debug("Callback signal from: %s", msg) if self._delay > 0 and not self.is_on: # Set timer to wait until updating the state def _delay_update(now): """Timer callback for sensor update.""" - _LOGGER.debug("%s Called delayed (%ssec) update", - self._name, self._delay) + _LOGGER.debug( + "%s Called delayed (%ssec) update", self._name, self._delay + ) self.schedule_update_ha_state() self._timer = None @@ -265,8 +283,8 @@ class HikvisionBinarySensor(BinarySensorDevice): self._timer = None self._timer = track_point_in_utc_time( - self._hass, _delay_update, - utcnow() + timedelta(seconds=self._delay)) + self._hass, _delay_update, utcnow() + timedelta(seconds=self._delay) + ) elif self._delay > 0 and self.is_on: # For delayed sensors kill any callbacks on true events and update diff --git a/homeassistant/components/binary_sensor/hive.py b/homeassistant/components/binary_sensor/hive.py index 68f326418..f6c51fed4 100644 --- a/homeassistant/components/binary_sensor/hive.py +++ b/homeassistant/components/binary_sensor/hive.py @@ -7,10 +7,9 @@ https://home-assistant.io/components/binary_sensor.hive/ from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.hive import DATA_HIVE -DEPENDENCIES = ['hive'] +DEPENDENCIES = ["hive"] -DEVICETYPE_DEVICE_CLASS = {'motionsensor': 'motion', - 'contactsensor': 'opening'} +DEVICETYPE_DEVICE_CLASS = {"motionsensor": "motion", "contactsensor": "opening"} def setup_platform(hass, config, add_entities, discovery_info=None): @@ -33,14 +32,13 @@ class HiveBinarySensorEntity(BinarySensorDevice): self.node_device_type = hivedevice["Hive_DeviceType"] self.session = hivesession self.attributes = {} - self.data_updatesource = '{}.{}'.format(self.device_type, - self.node_id) + self.data_updatesource = "{}.{}".format(self.device_type, self.node_id) self.session.entities.append(self) def handle_update(self, updatesource): """Handle the new update request.""" - if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: + if "{}.{}".format(self.device_type, self.node_id) not in updatesource: self.schedule_update_ha_state() @property @@ -61,11 +59,9 @@ class HiveBinarySensorEntity(BinarySensorDevice): @property def is_on(self): """Return true if the binary sensor is on.""" - return self.session.sensor.get_state(self.node_id, - self.node_device_type) + return self.session.sensor.get_state(self.node_id, self.node_device_type) def update(self): """Update all Node data from Hive.""" self.session.core.update_data(self.node_id) - self.attributes = self.session.attributes.state_attributes( - self.node_id) + self.attributes = self.session.attributes.state_attributes(self.node_id) diff --git a/homeassistant/components/binary_sensor/homematic.py b/homeassistant/components/binary_sensor/homematic.py index 9cfe4bbd6..80fa0df05 100644 --- a/homeassistant/components/binary_sensor/homematic.py +++ b/homeassistant/components/binary_sensor/homematic.py @@ -12,21 +12,21 @@ from homeassistant.const import STATE_UNKNOWN _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['homematic'] +DEPENDENCIES = ["homematic"] SENSOR_TYPES_CLASS = { - 'IPShutterContact': 'opening', - 'MaxShutterContact': 'opening', - 'Motion': 'motion', - 'MotionV2': 'motion', - 'PresenceIP': 'motion', - 'Remote': None, - 'RemoteMotion': None, - 'ShutterContact': 'opening', - 'Smoke': 'smoke', - 'SmokeV2': 'smoke', - 'TiltSensor': None, - 'WeatherSensor': None, + "IPShutterContact": "opening", + "MaxShutterContact": "opening", + "Motion": "motion", + "MotionV2": "motion", + "PresenceIP": "motion", + "Remote": None, + "RemoteMotion": None, + "ShutterContact": "opening", + "Smoke": "smoke", + "SmokeV2": "smoke", + "TiltSensor": None, + "WeatherSensor": None, } @@ -57,8 +57,8 @@ class HMBinarySensor(HMDevice, BinarySensorDevice): def device_class(self): """Return the class of this sensor from DEVICE_CLASSES.""" # If state is MOTION (Only RemoteMotion working) - if self._state == 'MOTION': - return 'motion' + if self._state == "MOTION": + return "motion" return SENSOR_TYPES_CLASS.get(self._hmdevice.__class__.__name__, None) def _init_data_struct(self): diff --git a/homeassistant/components/binary_sensor/homematicip_cloud.py b/homeassistant/components/binary_sensor/homematicip_cloud.py index dd22a8355..0b1d87853 100644 --- a/homeassistant/components/binary_sensor/homematicip_cloud.py +++ b/homeassistant/components/binary_sensor/homematicip_cloud.py @@ -8,18 +8,19 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.homematicip_cloud import ( - HMIPC_HAPID, HomematicipGenericDevice) + HMIPC_HAPID, + HomematicipGenericDevice, +) from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN -DEPENDENCIES = ['homematicip_cloud'] +DEPENDENCIES = ["homematicip_cloud"] _LOGGER = logging.getLogger(__name__) -STATE_SMOKE_OFF = 'IDLE_OFF' +STATE_SMOKE_OFF = "IDLE_OFF" -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 HomematicIP Cloud binary sensor devices.""" pass @@ -27,7 +28,10 @@ async def async_setup_platform( async def async_setup_entry(hass, config_entry, async_add_entities): """Set up the HomematicIP Cloud binary sensor from a config entry.""" from homematicip.aio.device import ( - AsyncShutterContact, AsyncMotionDetectorIndoor, AsyncSmokeDetector) + AsyncShutterContact, + AsyncMotionDetectorIndoor, + AsyncSmokeDetector, + ) home = hass.data[HMIPC_DOMAIN][config_entry.data[HMIPC_HAPID]].home devices = [] @@ -49,7 +53,7 @@ class HomematicipShutterContact(HomematicipGenericDevice, BinarySensorDevice): @property def device_class(self): """Return the class of this sensor.""" - return 'door' + return "door" @property def is_on(self): @@ -69,7 +73,7 @@ class HomematicipMotionDetector(HomematicipGenericDevice, BinarySensorDevice): @property def device_class(self): """Return the class of this sensor.""" - return 'motion' + return "motion" @property def is_on(self): @@ -85,7 +89,7 @@ class HomematicipSmokeDetector(HomematicipGenericDevice, BinarySensorDevice): @property def device_class(self): """Return the class of this sensor.""" - return 'smoke' + return "smoke" @property def is_on(self): diff --git a/homeassistant/components/binary_sensor/hydrawise.py b/homeassistant/components/binary_sensor/hydrawise.py index 38b660c50..6cedc30ed 100644 --- a/homeassistant/components/binary_sensor/hydrawise.py +++ b/homeassistant/components/binary_sensor/hydrawise.py @@ -10,20 +10,26 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.hydrawise import ( - BINARY_SENSORS, DATA_HYDRAWISE, HydrawiseEntity, DEVICE_MAP, - DEVICE_MAP_INDEX) -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) + BINARY_SENSORS, + DATA_HYDRAWISE, + HydrawiseEntity, + DEVICE_MAP, + DEVICE_MAP_INDEX, +) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_MONITORED_CONDITIONS -DEPENDENCIES = ['hydrawise'] +DEPENDENCIES = ["hydrawise"] _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_MONITORED_CONDITIONS, default=BINARY_SENSORS): - vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_MONITORED_CONDITIONS, default=BINARY_SENSORS): vol.All( + cv.ensure_list, [vol.In(BINARY_SENSORS)] + ) + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -32,17 +38,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensors = [] for sensor_type in config.get(CONF_MONITORED_CONDITIONS): - if sensor_type in ['status', 'rain_sensor']: + if sensor_type in ["status", "rain_sensor"]: sensors.append( - HydrawiseBinarySensor( - hydrawise.controller_status, sensor_type)) + HydrawiseBinarySensor(hydrawise.controller_status, sensor_type) + ) else: # create a sensor for each zone for zone in hydrawise.relays: zone_data = zone - zone_data['running'] = \ - hydrawise.controller_status.get('running', False) + zone_data["running"] = hydrawise.controller_status.get("running", False) sensors.append(HydrawiseBinarySensor(zone_data, sensor_type)) add_entities(sensors, True) @@ -60,16 +65,16 @@ class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorDevice): """Get the latest data and updates the state.""" _LOGGER.debug("Updating Hydrawise binary sensor: %s", self._name) mydata = self.hass.data[DATA_HYDRAWISE].data - if self._sensor_type == 'status': - self._state = mydata.status == 'All good!' - elif self._sensor_type == 'rain_sensor': + if self._sensor_type == "status": + self._state = mydata.status == "All good!" + elif self._sensor_type == "rain_sensor": for sensor in mydata.sensors: - if sensor['name'] == 'Rain': - self._state = sensor['active'] == 1 - elif self._sensor_type == 'is_watering': + if sensor["name"] == "Rain": + self._state = sensor["active"] == 1 + elif self._sensor_type == "is_watering": if not mydata.running: self._state = False - elif int(mydata.running[0]['relay']) == self.data['relay']: + elif int(mydata.running[0]["relay"]) == self.data["relay"]: self._state = True else: self._state = False @@ -78,4 +83,5 @@ class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorDevice): def device_class(self): """Return the device class of the sensor type.""" return DEVICE_MAP[self._sensor_type][ - DEVICE_MAP_INDEX.index('DEVICE_CLASS_INDEX')] + DEVICE_MAP_INDEX.index("DEVICE_CLASS_INDEX") + ] diff --git a/homeassistant/components/binary_sensor/ihc.py b/homeassistant/components/binary_sensor/ihc.py index 20937af6b..68c01a088 100644 --- a/homeassistant/components/binary_sensor/ihc.py +++ b/homeassistant/components/binary_sensor/ihc.py @@ -6,28 +6,41 @@ https://home-assistant.io/components/binary_sensor.ihc/ import voluptuous as vol from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA) + BinarySensorDevice, + PLATFORM_SCHEMA, + DEVICE_CLASSES_SCHEMA, +) from homeassistant.components.ihc import ( - validate_name, IHC_DATA, IHC_CONTROLLER, IHC_INFO) + validate_name, + IHC_DATA, + IHC_CONTROLLER, + IHC_INFO, +) from homeassistant.components.ihc.const import CONF_INVERTING from homeassistant.components.ihc.ihcdevice import IHCDevice -from homeassistant.const import ( - CONF_NAME, CONF_TYPE, CONF_ID, CONF_BINARY_SENSORS) +from homeassistant.const import CONF_NAME, CONF_TYPE, CONF_ID, CONF_BINARY_SENSORS import homeassistant.helpers.config_validation as cv -DEPENDENCIES = ['ihc'] +DEPENDENCIES = ["ihc"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_BINARY_SENSORS, default=[]): - vol.All(cv.ensure_list, [ - vol.All({ - vol.Required(CONF_ID): cv.positive_int, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_TYPE): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_INVERTING, default=False): cv.boolean, - }, validate_name) - ]) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_BINARY_SENSORS, default=[]): vol.All( + cv.ensure_list, + [ + vol.All( + { + vol.Required(CONF_ID): cv.positive_int, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_TYPE): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_INVERTING, default=False): cv.boolean, + }, + validate_name, + ) + ], + ) + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -37,13 +50,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): devices = [] if discovery_info: for name, device in discovery_info.items(): - ihc_id = device['ihc_id'] - product_cfg = device['product_cfg'] - product = device['product'] - sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info, - product_cfg.get(CONF_TYPE), - product_cfg[CONF_INVERTING], - product) + ihc_id = device["ihc_id"] + product_cfg = device["product_cfg"] + product = device["product"] + sensor = IHCBinarySensor( + ihc_controller, + name, + ihc_id, + info, + product_cfg.get(CONF_TYPE), + product_cfg[CONF_INVERTING], + product, + ) devices.append(sensor) else: binary_sensors = config[CONF_BINARY_SENSORS] @@ -52,8 +70,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): name = sensor_cfg[CONF_NAME] sensor_type = sensor_cfg.get(CONF_TYPE) inverting = sensor_cfg[CONF_INVERTING] - sensor = IHCBinarySensor(ihc_controller, name, ihc_id, info, - sensor_type, inverting) + sensor = IHCBinarySensor( + ihc_controller, name, ihc_id, info, sensor_type, inverting + ) devices.append(sensor) add_entities(devices) @@ -66,9 +85,16 @@ class IHCBinarySensor(IHCDevice, BinarySensorDevice): or function block, but it must be a boolean ON/OFF resources. """ - def __init__(self, ihc_controller, name, ihc_id: int, info: bool, - sensor_type: str, inverting: bool, - product=None) -> None: + def __init__( + self, + ihc_controller, + name, + ihc_id: int, + info: bool, + sensor_type: str, + inverting: bool, + product=None, + ) -> None: """Initialize the IHC binary sensor.""" super().__init__(ihc_controller, name, ihc_id, info, product) self._state = None diff --git a/homeassistant/components/binary_sensor/insteon.py b/homeassistant/components/binary_sensor/insteon.py index 533ff2d76..ff33e1ff0 100644 --- a/homeassistant/components/binary_sensor/insteon.py +++ b/homeassistant/components/binary_sensor/insteon.py @@ -10,31 +10,35 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.insteon import InsteonEntity -DEPENDENCIES = ['insteon'] +DEPENDENCIES = ["insteon"] _LOGGER = logging.getLogger(__name__) -SENSOR_TYPES = {'openClosedSensor': 'opening', - 'motionSensor': 'motion', - 'doorSensor': 'door', - 'wetLeakSensor': 'moisture', - 'lightSensor': 'light', - 'batterySensor': 'battery'} +SENSOR_TYPES = { + "openClosedSensor": "opening", + "motionSensor": "motion", + "doorSensor": "door", + "wetLeakSensor": "moisture", + "lightSensor": "light", + "batterySensor": "battery", +} @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 INSTEON device class for the hass platform.""" - insteon_modem = hass.data['insteon'].get('modem') + insteon_modem = hass.data["insteon"].get("modem") - address = discovery_info['address'] + address = discovery_info["address"] device = insteon_modem.devices[address] - state_key = discovery_info['state_key'] + state_key = discovery_info["state_key"] name = device.states[state_key].name - if name != 'dryLeakSensor': - _LOGGER.debug('Adding device %s entity %s to Binary Sensor platform', - device.address.hex, device.states[state_key].name) + if name != "dryLeakSensor": + _LOGGER.debug( + "Adding device %s entity %s to Binary Sensor platform", + device.address.hex, + device.states[state_key].name, + ) new_entity = InsteonBinarySensor(device, state_key) @@ -59,7 +63,7 @@ class InsteonBinarySensor(InsteonEntity, BinarySensorDevice): """Return the boolean response if the node is on.""" on_val = bool(self._insteon_device_state.value) - if self._insteon_device_state.name == 'lightSensor': + if self._insteon_device_state.name == "lightSensor": return not on_val return on_val diff --git a/homeassistant/components/binary_sensor/iss.py b/homeassistant/components/binary_sensor/iss.py index b986c51dd..8d40cf73f 100644 --- a/homeassistant/components/binary_sensor/iss.py +++ b/homeassistant/components/binary_sensor/iss.py @@ -11,28 +11,33 @@ import requests import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.const import ( - CONF_NAME, ATTR_LONGITUDE, ATTR_LATITUDE, CONF_SHOW_ON_MAP) + CONF_NAME, + ATTR_LONGITUDE, + ATTR_LATITUDE, + CONF_SHOW_ON_MAP, +) from homeassistant.util import Throttle -REQUIREMENTS = ['pyiss==1.0.1'] +REQUIREMENTS = ["pyiss==1.0.1"] _LOGGER = logging.getLogger(__name__) -ATTR_ISS_NEXT_RISE = 'next_rise' -ATTR_ISS_NUMBER_PEOPLE_SPACE = 'number_of_people_in_space' +ATTR_ISS_NEXT_RISE = "next_rise" +ATTR_ISS_NUMBER_PEOPLE_SPACE = "number_of_people_in_space" -DEFAULT_NAME = 'ISS' -DEFAULT_DEVICE_CLASS = 'visible' +DEFAULT_NAME = "ISS" +DEFAULT_DEVICE_CLASS = "visible" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_SHOW_ON_MAP, default=False): cv.boolean, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -84,16 +89,15 @@ class IssBinarySensor(BinarySensorDevice): """Return the state attributes.""" if self.iss_data: attrs = { - ATTR_ISS_NUMBER_PEOPLE_SPACE: - self.iss_data.number_of_people_in_space, + ATTR_ISS_NUMBER_PEOPLE_SPACE: self.iss_data.number_of_people_in_space, ATTR_ISS_NEXT_RISE: self.iss_data.next_rise, } if self._show_on_map: - attrs[ATTR_LONGITUDE] = self.iss_data.position.get('longitude') - attrs[ATTR_LATITUDE] = self.iss_data.position.get('latitude') + attrs[ATTR_LONGITUDE] = self.iss_data.position.get("longitude") + attrs[ATTR_LATITUDE] = self.iss_data.position.get("latitude") else: - attrs['long'] = self.iss_data.position.get('longitude') - attrs['lat'] = self.iss_data.position.get('latitude') + attrs["long"] = self.iss_data.position.get("longitude") + attrs["lat"] = self.iss_data.position.get("latitude") return attrs def update(self): diff --git a/homeassistant/components/binary_sensor/isy994.py b/homeassistant/components/binary_sensor/isy994.py index 36dacb067..ededf3ec3 100644 --- a/homeassistant/components/binary_sensor/isy994.py +++ b/homeassistant/components/binary_sensor/isy994.py @@ -12,8 +12,7 @@ from typing import Callable from homeassistant.core import callback from homeassistant.components.binary_sensor import BinarySensorDevice, DOMAIN -from homeassistant.components.isy994 import (ISY994_NODES, ISY994_PROGRAMS, - ISYDevice) +from homeassistant.components.isy994 import ISY994_NODES, ISY994_PROGRAMS, ISYDevice from homeassistant.const import STATE_ON, STATE_OFF from homeassistant.helpers.typing import ConfigType from homeassistant.helpers.event import async_track_point_in_utc_time @@ -22,14 +21,15 @@ from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) ISY_DEVICE_TYPES = { - 'moisture': ['16.8', '16.13', '16.14'], - 'opening': ['16.9', '16.6', '16.7', '16.2', '16.17', '16.20', '16.21'], - 'motion': ['16.1', '16.4', '16.5', '16.3'] + "moisture": ["16.8", "16.13", "16.14"], + "opening": ["16.9", "16.6", "16.7", "16.2", "16.17", "16.20", "16.21"], + "motion": ["16.1", "16.4", "16.5", "16.3"], } -def setup_platform(hass, config: ConfigType, - add_entities: Callable[[list], None], discovery_info=None): +def setup_platform( + hass, config: ConfigType, add_entities: Callable[[list], None], discovery_info=None +): """Set up the ISY994 binary sensor platform.""" devices = [] devices_by_nid = {} @@ -49,13 +49,16 @@ def setup_platform(hass, config: ConfigType, try: parent_device = devices_by_nid[node.parent_node.nid] except KeyError: - _LOGGER.error("Node %s has a parent node %s, but no device " - "was created for the parent. Skipping.", - node.nid, node.parent_nid) + _LOGGER.error( + "Node %s has a parent node %s, but no device " + "was created for the parent. Skipping.", + node.nid, + node.parent_nid, + ) else: device_type = _detect_device_type(node) subnode_id = int(node.nid[-1]) - if device_type in ('opening', 'moisture'): + if device_type in ("opening", "moisture"): # These sensors use an optional "negative" subnode 2 to snag # all state changes if subnode_id == 2: @@ -85,9 +88,9 @@ def _detect_device_type(node) -> str: # The type attribute didn't exist in the ISY's API response return None - split_type = device_type.split('.') + split_type = device_type.split(".") for device_class, ids in ISY_DEVICE_TYPES.items(): - if '{}.{}'.format(split_type[0], split_type[1]) in ids: + if "{}.{}".format(split_type[0], split_type[1]) in ids: return device_class return None @@ -95,7 +98,7 @@ def _detect_device_type(node) -> str: def _is_val_unknown(val): """Determine if a number value represents UNKNOWN from PyISY.""" - return val == -1*float('inf') + return val == -1 * float("inf") class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice): @@ -130,7 +133,8 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice): if self._negative_node is not None: self._negative_node.controlEvents.subscribe( - self._negative_node_control_handler) + self._negative_node_control_handler + ) def add_heartbeat_device(self, device) -> None: """Register a heartbeat device for this sensor. @@ -167,9 +171,11 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice): def _negative_node_control_handler(self, event: object) -> None: """Handle an "On" control event from the "negative" node.""" - if event == 'DON': - _LOGGER.debug("Sensor %s turning Off via the Negative node " - "sending a DON command", self.name) + if event == "DON": + _LOGGER.debug( + "Sensor %s turning Off via the Negative node " "sending a DON command", + self.name, + ) self._computed_state = False self.schedule_update_ha_state() self._heartbeat() @@ -181,15 +187,19 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice): will come to this node, with the negative node representing Off events """ - if event == 'DON': - _LOGGER.debug("Sensor %s turning On via the Primary node " - "sending a DON command", self.name) + if event == "DON": + _LOGGER.debug( + "Sensor %s turning On via the Primary node " "sending a DON command", + self.name, + ) self._computed_state = True self.schedule_update_ha_state() self._heartbeat() - if event == 'DOF': - _LOGGER.debug("Sensor %s turning Off via the Primary node " - "sending a DOF command", self.name) + if event == "DOF": + _LOGGER.debug( + "Sensor %s turning Off via the Primary node " "sending a DOF command", + self.name, + ) self._computed_state = False self.schedule_update_ha_state() self._heartbeat() @@ -222,7 +232,7 @@ class ISYBinarySensorDevice(ISYDevice, BinarySensorDevice): # Do this first so we don't invert None on moisture sensors return None - if self.device_class == 'moisture': + if self.device_class == "moisture": return not self._computed_state return self._computed_state @@ -266,15 +276,14 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice): """Subscribe to the node and subnode event emitters.""" yield from super().async_added_to_hass() - self._node.controlEvents.subscribe( - self._heartbeat_node_control_handler) + self._node.controlEvents.subscribe(self._heartbeat_node_control_handler) # Start the timer on bootup, so we can change from UNKNOWN to ON self._restart_timer() def _heartbeat_node_control_handler(self, event: object) -> None: """Update the heartbeat timestamp when an On event is sent.""" - if event == 'DON': + if event == "DON": self.heartbeat() def heartbeat(self): @@ -306,11 +315,13 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice): self.schedule_update_ha_state() point_in_time = dt_util.utcnow() + timedelta(hours=25) - _LOGGER.debug("Timer starting. Now: %s Then: %s", - dt_util.utcnow(), point_in_time) + _LOGGER.debug( + "Timer starting. Now: %s Then: %s", dt_util.utcnow(), point_in_time + ) self._heartbeat_timer = async_track_point_in_utc_time( - self.hass, timer_elapsed, point_in_time) + self.hass, timer_elapsed, point_in_time + ) def on_update(self, event: object) -> None: """Ignore node status updates. @@ -342,13 +353,13 @@ class ISYBinarySensorHeartbeat(ISYDevice, BinarySensorDevice): @property def device_class(self) -> str: """Get the class of this device.""" - return 'battery' + return "battery" @property def device_state_attributes(self): """Get the state attributes for the device.""" attr = super().device_state_attributes - attr['parent_entity_id'] = self._parent_device.entity_id + attr["parent_entity_id"] = self._parent_device.entity_id return attr diff --git a/homeassistant/components/binary_sensor/knx.py b/homeassistant/components/binary_sensor/knx.py index d0707b0f0..2c3e646c4 100644 --- a/homeassistant/components/binary_sensor/knx.py +++ b/homeassistant/components/binary_sensor/knx.py @@ -7,55 +7,54 @@ https://home-assistant.io/components/binary_sensor.knx/ import voluptuous as vol -from homeassistant.components.binary_sensor import ( - PLATFORM_SCHEMA, BinarySensorDevice) -from homeassistant.components.knx import ( - ATTR_DISCOVER_DEVICES, DATA_KNX, KNXAutomation) +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice +from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX, KNXAutomation from homeassistant.const import CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -CONF_ADDRESS = 'address' -CONF_DEVICE_CLASS = 'device_class' -CONF_SIGNIFICANT_BIT = 'significant_bit' +CONF_ADDRESS = "address" +CONF_DEVICE_CLASS = "device_class" +CONF_SIGNIFICANT_BIT = "significant_bit" CONF_DEFAULT_SIGNIFICANT_BIT = 1 -CONF_AUTOMATION = 'automation' -CONF_HOOK = 'hook' -CONF_DEFAULT_HOOK = 'on' -CONF_COUNTER = 'counter' +CONF_AUTOMATION = "automation" +CONF_HOOK = "hook" +CONF_DEFAULT_HOOK = "on" +CONF_COUNTER = "counter" CONF_DEFAULT_COUNTER = 1 -CONF_ACTION = 'action' -CONF_RESET_AFTER = 'reset_after' +CONF_ACTION = "action" +CONF_RESET_AFTER = "reset_after" -CONF__ACTION = 'turn_off_action' +CONF__ACTION = "turn_off_action" -DEFAULT_NAME = 'KNX Binary Sensor' -DEPENDENCIES = ['knx'] +DEFAULT_NAME = "KNX Binary Sensor" +DEPENDENCIES = ["knx"] -AUTOMATION_SCHEMA = vol.Schema({ - vol.Optional(CONF_HOOK, default=CONF_DEFAULT_HOOK): cv.string, - vol.Optional(CONF_COUNTER, default=CONF_DEFAULT_COUNTER): cv.port, - vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA -}) - -AUTOMATIONS_SCHEMA = vol.All( - cv.ensure_list, - [AUTOMATION_SCHEMA] +AUTOMATION_SCHEMA = vol.Schema( + { + vol.Optional(CONF_HOOK, default=CONF_DEFAULT_HOOK): cv.string, + vol.Optional(CONF_COUNTER, default=CONF_DEFAULT_COUNTER): cv.port, + vol.Required(CONF_ACTION): cv.SCRIPT_SCHEMA, + } ) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ADDRESS): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_DEVICE_CLASS): cv.string, - vol.Optional(CONF_SIGNIFICANT_BIT, default=CONF_DEFAULT_SIGNIFICANT_BIT): - cv.positive_int, - vol.Optional(CONF_RESET_AFTER): cv.positive_int, - vol.Optional(CONF_AUTOMATION): AUTOMATIONS_SCHEMA, -}) +AUTOMATIONS_SCHEMA = vol.All(cv.ensure_list, [AUTOMATION_SCHEMA]) + +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ADDRESS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_DEVICE_CLASS): cv.string, + vol.Optional( + CONF_SIGNIFICANT_BIT, default=CONF_DEFAULT_SIGNIFICANT_BIT + ): cv.positive_int, + vol.Optional(CONF_RESET_AFTER): cv.positive_int, + vol.Optional(CONF_AUTOMATION): AUTOMATIONS_SCHEMA, + } +) -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 binary sensor(s) for KNX platform.""" if discovery_info is not None: async_add_entities_discovery(hass, discovery_info, async_add_entities) @@ -78,13 +77,15 @@ def async_add_entities_config(hass, config, async_add_entities): """Set up binary senor for KNX platform configured within platform.""" name = config.get(CONF_NAME) import xknx + binary_sensor = xknx.devices.BinarySensor( hass.data[DATA_KNX].xknx, name=name, group_address=config.get(CONF_ADDRESS), device_class=config.get(CONF_DEVICE_CLASS), significant_bit=config.get(CONF_SIGNIFICANT_BIT), - reset_after=config.get(CONF_RESET_AFTER)) + reset_after=config.get(CONF_RESET_AFTER), + ) hass.data[DATA_KNX].xknx.devices.add(binary_sensor) entity = KNXBinarySensor(hass, binary_sensor) @@ -94,9 +95,15 @@ def async_add_entities_config(hass, config, async_add_entities): counter = automation.get(CONF_COUNTER) hook = automation.get(CONF_HOOK) action = automation.get(CONF_ACTION) - entity.automations.append(KNXAutomation( - hass=hass, device=binary_sensor, hook=hook, - action=action, counter=counter)) + entity.automations.append( + KNXAutomation( + hass=hass, + device=binary_sensor, + hook=hook, + action=action, + counter=counter, + ) + ) async_add_entities([entity]) @@ -113,9 +120,11 @@ class KNXBinarySensor(BinarySensorDevice): @callback def async_register_callbacks(self): """Register callbacks to update hass after device was changed.""" + async def after_update_callback(device): """Call after device was updated.""" await self.async_update_ha_state() + self.device.register_device_updated_cb(after_update_callback) @property diff --git a/homeassistant/components/binary_sensor/konnected.py b/homeassistant/components/binary_sensor/konnected.py index e91d3f613..5d30253f5 100644 --- a/homeassistant/components/binary_sensor/konnected.py +++ b/homeassistant/components/binary_sensor/konnected.py @@ -8,29 +8,39 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.konnected import ( - DOMAIN as KONNECTED_DOMAIN, PIN_TO_ZONE, SIGNAL_SENSOR_UPDATE) + DOMAIN as KONNECTED_DOMAIN, + PIN_TO_ZONE, + SIGNAL_SENSOR_UPDATE, +) from homeassistant.const import ( - CONF_DEVICES, CONF_TYPE, CONF_NAME, CONF_BINARY_SENSORS, ATTR_ENTITY_ID, - ATTR_STATE) + CONF_DEVICES, + CONF_TYPE, + CONF_NAME, + CONF_BINARY_SENSORS, + ATTR_ENTITY_ID, + ATTR_STATE, +) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['konnected'] +DEPENDENCIES = ["konnected"] -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 binary sensors attached to a Konnected device.""" if discovery_info is None: return data = hass.data[KONNECTED_DOMAIN] - device_id = discovery_info['device_id'] - sensors = [KonnectedBinarySensor(device_id, pin_num, pin_data) - for pin_num, pin_data in - data[CONF_DEVICES][device_id][CONF_BINARY_SENSORS].items()] + device_id = discovery_info["device_id"] + sensors = [ + KonnectedBinarySensor(device_id, pin_num, pin_data) + for pin_num, pin_data in data[CONF_DEVICES][device_id][ + CONF_BINARY_SENSORS + ].items() + ] async_add_entities(sensors) @@ -44,9 +54,10 @@ class KonnectedBinarySensor(BinarySensorDevice): self._pin_num = pin_num self._state = self._data.get(ATTR_STATE) self._device_class = self._data.get(CONF_TYPE) - self._name = self._data.get(CONF_NAME, 'Konnected {} Zone {}'.format( - device_id, PIN_TO_ZONE[pin_num])) - _LOGGER.debug('Created new Konnected sensor: %s', self._name) + self._name = self._data.get( + CONF_NAME, "Konnected {} Zone {}".format(device_id, PIN_TO_ZONE[pin_num]) + ) + _LOGGER.debug("Created new Konnected sensor: %s", self._name) @property def name(self): @@ -72,8 +83,8 @@ class KonnectedBinarySensor(BinarySensorDevice): """Store entity_id and register state change callback.""" self._data[ATTR_ENTITY_ID] = self.entity_id async_dispatcher_connect( - self.hass, SIGNAL_SENSOR_UPDATE.format(self.entity_id), - self.async_set_state) + self.hass, SIGNAL_SENSOR_UPDATE.format(self.entity_id), self.async_set_state + ) @callback def async_set_state(self, state): diff --git a/homeassistant/components/binary_sensor/linode.py b/homeassistant/components/binary_sensor/linode.py index 24abc3dd8..4cebc33ce 100644 --- a/homeassistant/components/binary_sensor/linode.py +++ b/homeassistant/components/binary_sensor/linode.py @@ -9,22 +9,29 @@ import logging import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.components.linode import ( - CONF_NODES, ATTR_CREATED, ATTR_NODE_ID, ATTR_NODE_NAME, - ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, - ATTR_REGION, ATTR_VCPUS, DATA_LINODE) + CONF_NODES, + ATTR_CREATED, + ATTR_NODE_ID, + ATTR_NODE_NAME, + ATTR_IPV4_ADDRESS, + ATTR_IPV6_ADDRESS, + ATTR_MEMORY, + ATTR_REGION, + ATTR_VCPUS, + DATA_LINODE, +) _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Node' -DEFAULT_DEVICE_CLASS = 'moving' -DEPENDENCIES = ['linode'] +DEFAULT_NAME = "Node" +DEFAULT_DEVICE_CLASS = "moving" +DEPENDENCIES = ["linode"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_NODES): vol.All(cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_NODES): vol.All(cv.ensure_list, [cv.string])} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -83,7 +90,7 @@ class LinodeBinarySensor(BinarySensorDevice): if node.id == self._node_id: self.data = node if self.data is not None: - self._state = self.data.status == 'running' + self._state = self.data.status == "running" self._attrs = { ATTR_CREATED: self.data.created, ATTR_NODE_ID: self.data.id, diff --git a/homeassistant/components/binary_sensor/maxcube.py b/homeassistant/components/binary_sensor/maxcube.py index 6bb9278d8..1fbc18a33 100644 --- a/homeassistant/components/binary_sensor/maxcube.py +++ b/homeassistant/components/binary_sensor/maxcube.py @@ -19,13 +19,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for handler in hass.data[DATA_KEY].values(): cube = handler.cube for device in cube.devices: - name = "{} {}".format( - cube.room_by_id(device.room_id).name, device.name) + name = "{} {}".format(cube.room_by_id(device.room_id).name, device.name) # Only add Window Shutters if cube.is_windowshutter(device): - devices.append( - MaxCubeShutter(handler, name, device.rf_address)) + devices.append(MaxCubeShutter(handler, name, device.rf_address)) if devices: add_entities(devices) @@ -37,7 +35,7 @@ class MaxCubeShutter(BinarySensorDevice): def __init__(self, handler, name, rf_address): """Initialize MAX! Cube BinarySensorDevice.""" self._name = name - self._sensor_type = 'window' + self._sensor_type = "window" self._rf_address = rf_address self._cubehandle = handler self._state = STATE_UNKNOWN diff --git a/homeassistant/components/binary_sensor/modbus.py b/homeassistant/components/binary_sensor/modbus.py index f9f259759..99c8bb4dc 100644 --- a/homeassistant/components/binary_sensor/modbus.py +++ b/homeassistant/components/binary_sensor/modbus.py @@ -14,28 +14,33 @@ from homeassistant.helpers import config_validation as cv from homeassistant.components.sensor import PLATFORM_SCHEMA _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['modbus'] +DEPENDENCIES = ["modbus"] -CONF_COIL = 'coil' -CONF_COILS = 'coils' +CONF_COIL = "coil" +CONF_COILS = "coils" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COILS): [{ - vol.Required(CONF_COIL): cv.positive_int, - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_SLAVE): cv.positive_int - }] -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_COILS): [ + { + vol.Required(CONF_COIL): cv.positive_int, + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_SLAVE): cv.positive_int, + } + ] + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Modbus binary sensors.""" sensors = [] for coil in config.get(CONF_COILS): - sensors.append(ModbusCoilSensor( - coil.get(CONF_NAME), - coil.get(CONF_SLAVE), - coil.get(CONF_COIL))) + sensors.append( + ModbusCoilSensor( + coil.get(CONF_NAME), coil.get(CONF_SLAVE), coil.get(CONF_COIL) + ) + ) add_entities(sensors) @@ -66,6 +71,5 @@ class ModbusCoilSensor(BinarySensorDevice): self._value = result.bits[0] except AttributeError: _LOGGER.error( - 'No response from modbus slave %s coil %s', - self._slave, - self._coil) + "No response from modbus slave %s coil %s", self._slave, self._coil + ) diff --git a/homeassistant/components/binary_sensor/mqtt.py b/homeassistant/components/binary_sensor/mqtt.py index 37a26a272..0d6b89149 100644 --- a/homeassistant/components/binary_sensor/mqtt.py +++ b/homeassistant/components/binary_sensor/mqtt.py @@ -13,40 +13,53 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.components import mqtt from homeassistant.components.binary_sensor import ( - BinarySensorDevice, DEVICE_CLASSES_SCHEMA) + BinarySensorDevice, + DEVICE_CLASSES_SCHEMA, +) from homeassistant.const import ( - CONF_FORCE_UPDATE, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_PAYLOAD_ON, - CONF_PAYLOAD_OFF, CONF_DEVICE_CLASS) + CONF_FORCE_UPDATE, + CONF_NAME, + CONF_VALUE_TEMPLATE, + CONF_PAYLOAD_ON, + CONF_PAYLOAD_OFF, + CONF_DEVICE_CLASS, +) from homeassistant.components.mqtt import ( - CONF_STATE_TOPIC, CONF_AVAILABILITY_TOPIC, CONF_PAYLOAD_AVAILABLE, - CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, MqttAvailability) + CONF_STATE_TOPIC, + CONF_AVAILABILITY_TOPIC, + CONF_PAYLOAD_AVAILABLE, + CONF_PAYLOAD_NOT_AVAILABLE, + CONF_QOS, + MqttAvailability, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'MQTT Binary sensor' -CONF_UNIQUE_ID = 'unique_id' -DEFAULT_PAYLOAD_OFF = 'OFF' -DEFAULT_PAYLOAD_ON = 'ON' +DEFAULT_NAME = "MQTT Binary sensor" +CONF_UNIQUE_ID = "unique_id" +DEFAULT_PAYLOAD_OFF = "OFF" +DEFAULT_PAYLOAD_ON = "ON" DEFAULT_FORCE_UPDATE = False -DEPENDENCIES = ['mqtt'] +DEPENDENCIES = ["mqtt"] -PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, - vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, - # Integrations shouldn't never expose unique_id through configuration - # this here is an exception because MQTT is a msg transport, not a protocol - vol.Optional(CONF_UNIQUE_ID): cv.string, -}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) +PLATFORM_SCHEMA = mqtt.MQTT_RO_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_OFF, default=DEFAULT_PAYLOAD_OFF): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default=DEFAULT_PAYLOAD_ON): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_FORCE_UPDATE, default=DEFAULT_FORCE_UPDATE): cv.boolean, + # Integrations shouldn't never expose unique_id through configuration + # this here is an exception because MQTT is a msg transport, not a protocol + vol.Optional(CONF_UNIQUE_ID): cv.string, + } +).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) @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 MQTT binary sensor.""" if discovery_info is not None: config = PLATFORM_SCHEMA(discovery_info) @@ -55,32 +68,48 @@ def async_setup_platform(hass, config, async_add_entities, if value_template is not None: value_template.hass = hass - async_add_entities([MqttBinarySensor( - config.get(CONF_NAME), - config.get(CONF_STATE_TOPIC), - config.get(CONF_AVAILABILITY_TOPIC), - config.get(CONF_DEVICE_CLASS), - config.get(CONF_QOS), - config.get(CONF_FORCE_UPDATE), - config.get(CONF_PAYLOAD_ON), - config.get(CONF_PAYLOAD_OFF), - config.get(CONF_PAYLOAD_AVAILABLE), - config.get(CONF_PAYLOAD_NOT_AVAILABLE), - value_template, - config.get(CONF_UNIQUE_ID), - )]) + async_add_entities( + [ + MqttBinarySensor( + config.get(CONF_NAME), + config.get(CONF_STATE_TOPIC), + config.get(CONF_AVAILABILITY_TOPIC), + config.get(CONF_DEVICE_CLASS), + config.get(CONF_QOS), + config.get(CONF_FORCE_UPDATE), + config.get(CONF_PAYLOAD_ON), + config.get(CONF_PAYLOAD_OFF), + config.get(CONF_PAYLOAD_AVAILABLE), + config.get(CONF_PAYLOAD_NOT_AVAILABLE), + value_template, + config.get(CONF_UNIQUE_ID), + ) + ] + ) class MqttBinarySensor(MqttAvailability, BinarySensorDevice): """Representation a binary sensor that is updated by MQTT.""" - def __init__(self, name, state_topic, availability_topic, device_class, - qos, force_update, payload_on, payload_off, payload_available, - payload_not_available, value_template, - unique_id: Optional[str]): + def __init__( + self, + name, + state_topic, + availability_topic, + device_class, + qos, + force_update, + payload_on, + payload_off, + payload_available, + payload_not_available, + value_template, + unique_id: Optional[str], + ): """Initialize the MQTT binary sensor.""" - super().__init__(availability_topic, qos, payload_available, - payload_not_available) + super().__init__( + availability_topic, qos, payload_available, payload_not_available + ) self._name = name self._state = None self._state_topic = state_topic @@ -101,22 +130,24 @@ class MqttBinarySensor(MqttAvailability, BinarySensorDevice): def state_message_received(topic, payload, qos): """Handle a new received MQTT state message.""" if self._template is not None: - payload = self._template.async_render_with_possible_json_value( - payload) + payload = self._template.async_render_with_possible_json_value(payload) if payload == self._payload_on: self._state = True elif payload == self._payload_off: self._state = False else: # Payload is not for this entity - _LOGGER.warning('No matching payload found' - ' for entity: %s with state_topic: %s', - self._name, self._state_topic) + _LOGGER.warning( + "No matching payload found" " for entity: %s with state_topic: %s", + self._name, + self._state_topic, + ) return self.async_schedule_update_ha_state() yield from mqtt.async_subscribe( - self.hass, self._state_topic, state_message_received, self._qos) + self.hass, self._state_topic, state_message_received, self._qos + ) @property def should_poll(self): diff --git a/homeassistant/components/binary_sensor/mychevy.py b/homeassistant/components/binary_sensor/mychevy.py index d1438379d..7c4fe2a41 100644 --- a/homeassistant/components/binary_sensor/mychevy.py +++ b/homeassistant/components/binary_sensor/mychevy.py @@ -8,23 +8,21 @@ import asyncio import logging from homeassistant.components.mychevy import ( - EVBinarySensorConfig, DOMAIN as MYCHEVY_DOMAIN, UPDATE_TOPIC + EVBinarySensorConfig, + DOMAIN as MYCHEVY_DOMAIN, + UPDATE_TOPIC, ) -from homeassistant.components.binary_sensor import ( - ENTITY_ID_FORMAT, BinarySensorDevice) +from homeassistant.components.binary_sensor import ENTITY_ID_FORMAT, BinarySensorDevice from homeassistant.core import callback from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) -SENSORS = [ - EVBinarySensorConfig("Plugged In", "plugged_in", "plug") -] +SENSORS = [EVBinarySensorConfig("Plugged In", "plugged_in", "plug")] @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 MyChevy sensors.""" if discovery_info is None: return @@ -56,9 +54,10 @@ class EVBinarySensor(BinarySensorDevice): self._is_on = None self._car_vid = car_vid self.entity_id = ENTITY_ID_FORMAT.format( - '{}_{}_{}'.format(MYCHEVY_DOMAIN, - slugify(self._car.name), - slugify(self._name))) + "{}_{}_{}".format( + MYCHEVY_DOMAIN, slugify(self._car.name), slugify(self._name) + ) + ) @property def name(self): @@ -79,7 +78,8 @@ class EVBinarySensor(BinarySensorDevice): def async_added_to_hass(self): """Register callbacks.""" self.hass.helpers.dispatcher.async_dispatcher_connect( - UPDATE_TOPIC, self.async_update_callback) + UPDATE_TOPIC, self.async_update_callback + ) @callback def async_update_callback(self): diff --git a/homeassistant/components/binary_sensor/mysensors.py b/homeassistant/components/binary_sensor/mysensors.py index f0b7832cf..a9e41139c 100644 --- a/homeassistant/components/binary_sensor/mysensors.py +++ b/homeassistant/components/binary_sensor/mysensors.py @@ -6,31 +6,36 @@ https://home-assistant.io/components/binary_sensor.mysensors/ """ from homeassistant.components import mysensors from homeassistant.components.binary_sensor import ( - DEVICE_CLASSES, DOMAIN, BinarySensorDevice) + DEVICE_CLASSES, + DOMAIN, + BinarySensorDevice, +) from homeassistant.const import STATE_ON SENSORS = { - 'S_DOOR': 'door', - 'S_MOTION': 'motion', - 'S_SMOKE': 'smoke', - 'S_SPRINKLER': 'safety', - 'S_WATER_LEAK': 'safety', - 'S_SOUND': 'sound', - 'S_VIBRATION': 'vibration', - 'S_MOISTURE': 'moisture', + "S_DOOR": "door", + "S_MOTION": "motion", + "S_SMOKE": "smoke", + "S_SPRINKLER": "safety", + "S_WATER_LEAK": "safety", + "S_SOUND": "sound", + "S_VIBRATION": "vibration", + "S_MOISTURE": "moisture", } -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 mysensors platform for binary sensors.""" mysensors.setup_mysensors_platform( - hass, DOMAIN, discovery_info, MySensorsBinarySensor, - async_add_entities=async_add_entities) + hass, + DOMAIN, + discovery_info, + MySensorsBinarySensor, + async_add_entities=async_add_entities, + ) -class MySensorsBinarySensor( - mysensors.device.MySensorsEntity, BinarySensorDevice): +class MySensorsBinarySensor(mysensors.device.MySensorsEntity, BinarySensorDevice): """Representation of a MySensors Binary Sensor child node.""" @property diff --git a/homeassistant/components/binary_sensor/mystrom.py b/homeassistant/components/binary_sensor/mystrom.py index 23f40ce0a..2937d1391 100644 --- a/homeassistant/components/binary_sensor/mystrom.py +++ b/homeassistant/components/binary_sensor/mystrom.py @@ -13,12 +13,11 @@ from homeassistant.const import HTTP_UNPROCESSABLE_ENTITY _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['http'] +DEPENDENCIES = ["http"] @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 myStrom Binary Sensor.""" hass.http.register_view(MyStromView(async_add_entities)) @@ -28,9 +27,9 @@ def async_setup_platform(hass, config, async_add_entities, class MyStromView(HomeAssistantView): """View to handle requests from myStrom buttons.""" - url = '/api/mystrom' - name = 'api:mystrom' - supported_actions = ['single', 'double', 'long', 'touch'] + url = "/api/mystrom" + name = "api:mystrom" + supported_actions = ["single", "double", "long", "touch"] def __init__(self, add_entities): """Initialize the myStrom URL endpoint.""" @@ -40,33 +39,36 @@ class MyStromView(HomeAssistantView): @asyncio.coroutine def get(self, request): """Handle the GET request received from a myStrom button.""" - res = yield from self._handle(request.app['hass'], request.query) + res = yield from self._handle(request.app["hass"], request.query) return res @asyncio.coroutine def _handle(self, hass, data): """Handle requests to the myStrom endpoint.""" - button_action = next(( - parameter for parameter in data - if parameter in self.supported_actions), None) + button_action = next( + (parameter for parameter in data if parameter in self.supported_actions), + None, + ) if button_action is None: - _LOGGER.error( - "Received unidentified message from myStrom button: %s", data) - return ("Received unidentified message: {}".format(data), - HTTP_UNPROCESSABLE_ENTITY) + _LOGGER.error("Received unidentified message from myStrom button: %s", data) + return ( + "Received unidentified message: {}".format(data), + HTTP_UNPROCESSABLE_ENTITY, + ) button_id = data[button_action] - entity_id = '{}.{}_{}'.format(DOMAIN, button_id, button_action) + entity_id = "{}.{}_{}".format(DOMAIN, button_id, button_action) if entity_id not in self.buttons: - _LOGGER.info("New myStrom button/action detected: %s/%s", - button_id, button_action) + _LOGGER.info( + "New myStrom button/action detected: %s/%s", button_id, button_action + ) self.buttons[entity_id] = MyStromBinarySensor( - '{}_{}'.format(button_id, button_action)) + "{}_{}".format(button_id, button_action) + ) self.add_entities([self.buttons[entity_id]]) else: - new_state = True if self.buttons[entity_id].state == 'off' \ - else False + new_state = True if self.buttons[entity_id].state == "off" else False self.buttons[entity_id].async_on_update(new_state) diff --git a/homeassistant/components/binary_sensor/nest.py b/homeassistant/components/binary_sensor/nest.py index c60463a86..041251a1f 100644 --- a/homeassistant/components/binary_sensor/nest.py +++ b/homeassistant/components/binary_sensor/nest.py @@ -9,47 +9,51 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.nest import ( - DATA_NEST, DATA_NEST_CONFIG, CONF_BINARY_SENSORS, NestSensorDevice) + DATA_NEST, + DATA_NEST_CONFIG, + CONF_BINARY_SENSORS, + NestSensorDevice, +) from homeassistant.const import CONF_MONITORED_CONDITIONS -DEPENDENCIES = ['nest'] +DEPENDENCIES = ["nest"] -BINARY_TYPES = {'online': 'connectivity'} +BINARY_TYPES = {"online": "connectivity"} CLIMATE_BINARY_TYPES = { - 'fan': None, - 'is_using_emergency_heat': 'heat', - 'is_locked': None, - 'has_leaf': None, + "fan": None, + "is_using_emergency_heat": "heat", + "is_locked": None, + "has_leaf": None, } CAMERA_BINARY_TYPES = { - 'motion_detected': 'motion', - 'sound_detected': 'sound', - 'person_detected': 'occupancy', + "motion_detected": "motion", + "sound_detected": "sound", + "person_detected": "occupancy", } -STRUCTURE_BINARY_TYPES = { - 'away': None, -} +STRUCTURE_BINARY_TYPES = {"away": None} -STRUCTURE_BINARY_STATE_MAP = { - 'away': {'away': True, 'home': False}, -} +STRUCTURE_BINARY_STATE_MAP = {"away": {"away": True, "home": False}} _BINARY_TYPES_DEPRECATED = [ - 'hvac_ac_state', - 'hvac_aux_heater_state', - 'hvac_heater_state', - 'hvac_heat_x2_state', - 'hvac_heat_x3_state', - 'hvac_alt_heat_state', - 'hvac_alt_heat_x2_state', - 'hvac_emer_heat_state', + "hvac_ac_state", + "hvac_aux_heater_state", + "hvac_heater_state", + "hvac_heat_x2_state", + "hvac_heat_x3_state", + "hvac_alt_heat_state", + "hvac_alt_heat_x2_state", + "hvac_emer_heat_state", ] -_VALID_BINARY_SENSOR_TYPES = {**BINARY_TYPES, **CLIMATE_BINARY_TYPES, - **CAMERA_BINARY_TYPES, **STRUCTURE_BINARY_TYPES} +_VALID_BINARY_SENSOR_TYPES = { + **BINARY_TYPES, + **CLIMATE_BINARY_TYPES, + **CAMERA_BINARY_TYPES, + **STRUCTURE_BINARY_TYPES, +} _LOGGER = logging.getLogger(__name__) @@ -65,8 +69,7 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up a Nest binary sensor based on a config entry.""" nest = hass.data[DATA_NEST] - discovery_info = \ - hass.data.get(DATA_NEST_CONFIG, {}).get(CONF_BINARY_SENSORS, {}) + discovery_info = hass.data.get(DATA_NEST_CONFIG, {}).get(CONF_BINARY_SENSORS, {}) # Add all available binary sensors if no Nest binary sensor config is set if discovery_info == {}: @@ -76,39 +79,46 @@ async def async_setup_entry(hass, entry, async_add_entities): for variable in conditions: if variable in _BINARY_TYPES_DEPRECATED: - wstr = (variable + " is no a longer supported " - "monitored_conditions. See " - "https://home-assistant.io/components/binary_sensor.nest/ " - "for valid options.") + wstr = ( + variable + " is no a longer supported " + "monitored_conditions. See " + "https://home-assistant.io/components/binary_sensor.nest/ " + "for valid options." + ) _LOGGER.error(wstr) def get_binary_sensors(): """Get the Nest binary sensors.""" sensors = [] for structure in nest.structures(): - sensors += [NestBinarySensor(structure, None, variable) - for variable in conditions - if variable in STRUCTURE_BINARY_TYPES] - device_chain = chain(nest.thermostats(), - nest.smoke_co_alarms(), - nest.cameras()) + sensors += [ + NestBinarySensor(structure, None, variable) + for variable in conditions + if variable in STRUCTURE_BINARY_TYPES + ] + device_chain = chain(nest.thermostats(), nest.smoke_co_alarms(), nest.cameras()) for structure, device in device_chain: - sensors += [NestBinarySensor(structure, device, variable) - for variable in conditions - if variable in BINARY_TYPES] - sensors += [NestBinarySensor(structure, device, variable) - for variable in conditions - if variable in CLIMATE_BINARY_TYPES - and device.is_thermostat] + sensors += [ + NestBinarySensor(structure, device, variable) + for variable in conditions + if variable in BINARY_TYPES + ] + sensors += [ + NestBinarySensor(structure, device, variable) + for variable in conditions + if variable in CLIMATE_BINARY_TYPES and device.is_thermostat + ] if device.is_camera: - sensors += [NestBinarySensor(structure, device, variable) - for variable in conditions - if variable in CAMERA_BINARY_TYPES] + sensors += [ + NestBinarySensor(structure, device, variable) + for variable in conditions + if variable in CAMERA_BINARY_TYPES + ] for activity_zone in device.activity_zones: - sensors += [NestActivityZoneSensor(structure, - device, - activity_zone)] + sensors += [ + NestActivityZoneSensor(structure, device, activity_zone) + ] return sensors @@ -132,8 +142,7 @@ class NestBinarySensor(NestSensorDevice, BinarySensorDevice): """Retrieve latest state.""" value = getattr(self.device, self.variable) if self.variable in STRUCTURE_BINARY_TYPES: - self._state = bool(STRUCTURE_BINARY_STATE_MAP - [self.variable].get(value)) + self._state = bool(STRUCTURE_BINARY_STATE_MAP[self.variable].get(value)) else: self._state = bool(value) @@ -150,7 +159,7 @@ class NestActivityZoneSensor(NestBinarySensor): @property def device_class(self): """Return the device class of the binary sensor.""" - return 'motion' + return "motion" def update(self): """Retrieve latest state.""" diff --git a/homeassistant/components/binary_sensor/netatmo.py b/homeassistant/components/binary_sensor/netatmo.py index 2cafacf40..c056374cc 100644 --- a/homeassistant/components/binary_sensor/netatmo.py +++ b/homeassistant/components/binary_sensor/netatmo.py @@ -10,15 +10,14 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.components.netatmo import CameraData from homeassistant.const import CONF_TIMEOUT from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['netatmo'] +DEPENDENCIES = ["netatmo"] # These are the available sensors mapped to binary_sensor class WELCOME_SENSOR_TYPES = { @@ -30,31 +29,31 @@ PRESENCE_SENSOR_TYPES = { "Outdoor motion": "motion", "Outdoor human": "motion", "Outdoor animal": "motion", - "Outdoor vehicle": "motion" -} -TAG_SENSOR_TYPES = { - "Tag Vibration": "vibration", - "Tag Open": "opening" + "Outdoor vehicle": "motion", } +TAG_SENSOR_TYPES = {"Tag Vibration": "vibration", "Tag Open": "opening"} -CONF_HOME = 'home' -CONF_CAMERAS = 'cameras' -CONF_WELCOME_SENSORS = 'welcome_sensors' -CONF_PRESENCE_SENSORS = 'presence_sensors' -CONF_TAG_SENSORS = 'tag_sensors' +CONF_HOME = "home" +CONF_CAMERAS = "cameras" +CONF_WELCOME_SENSORS = "welcome_sensors" +CONF_PRESENCE_SENSORS = "presence_sensors" +CONF_TAG_SENSORS = "tag_sensors" DEFAULT_TIMEOUT = 90 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_CAMERAS, default=[]): - vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_HOME): cv.string, - vol.Optional(CONF_PRESENCE_SENSORS, default=list(PRESENCE_SENSOR_TYPES)): - vol.All(cv.ensure_list, [vol.In(PRESENCE_SENSOR_TYPES)]), - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - vol.Optional(CONF_WELCOME_SENSORS, default=list(WELCOME_SENSOR_TYPES)): - vol.All(cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_CAMERAS, default=[]): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_HOME): cv.string, + vol.Optional( + CONF_PRESENCE_SENSORS, default=list(PRESENCE_SENSOR_TYPES) + ): vol.All(cv.ensure_list, [vol.In(PRESENCE_SENSOR_TYPES)]), + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + vol.Optional(CONF_WELCOME_SENSORS, default=list(WELCOME_SENSOR_TYPES)): vol.All( + cv.ensure_list, [vol.In(WELCOME_SENSOR_TYPES)] + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -68,6 +67,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): module_name = None import pyatmo + try: data = CameraData(netatmo.NETATMO_AUTH, home) if not data.get_camera_names(): @@ -75,46 +75,82 @@ def setup_platform(hass, config, add_entities, discovery_info=None): except pyatmo.NoDevice: return None - welcome_sensors = config.get( - CONF_WELCOME_SENSORS, WELCOME_SENSOR_TYPES) - presence_sensors = config.get( - CONF_PRESENCE_SENSORS, PRESENCE_SENSOR_TYPES) + welcome_sensors = config.get(CONF_WELCOME_SENSORS, WELCOME_SENSOR_TYPES) + presence_sensors = config.get(CONF_PRESENCE_SENSORS, PRESENCE_SENSOR_TYPES) tag_sensors = config.get(CONF_TAG_SENSORS, TAG_SENSOR_TYPES) for camera_name in data.get_camera_names(): camera_type = data.get_camera_type(camera=camera_name, home=home) - if camera_type == 'NACamera': + if camera_type == "NACamera": if CONF_CAMERAS in config: - if config[CONF_CAMERAS] != [] and \ - camera_name not in config[CONF_CAMERAS]: + if ( + config[CONF_CAMERAS] != [] + and camera_name not in config[CONF_CAMERAS] + ): continue for variable in welcome_sensors: - add_entities([NetatmoBinarySensor( - data, camera_name, module_name, home, timeout, - camera_type, variable)], True) - if camera_type == 'NOC': + add_entities( + [ + NetatmoBinarySensor( + data, + camera_name, + module_name, + home, + timeout, + camera_type, + variable, + ) + ], + True, + ) + if camera_type == "NOC": if CONF_CAMERAS in config: - if config[CONF_CAMERAS] != [] and \ - camera_name not in config[CONF_CAMERAS]: + if ( + config[CONF_CAMERAS] != [] + and camera_name not in config[CONF_CAMERAS] + ): continue for variable in presence_sensors: - add_entities([NetatmoBinarySensor( - data, camera_name, module_name, home, timeout, - camera_type, variable)], True) + add_entities( + [ + NetatmoBinarySensor( + data, + camera_name, + module_name, + home, + timeout, + camera_type, + variable, + ) + ], + True, + ) for module_name in data.get_module_names(camera_name): for variable in tag_sensors: camera_type = None - add_entities([NetatmoBinarySensor( - data, camera_name, module_name, home, timeout, - camera_type, variable)], True) + add_entities( + [ + NetatmoBinarySensor( + data, + camera_name, + module_name, + home, + timeout, + camera_type, + variable, + ) + ], + True, + ) class NetatmoBinarySensor(BinarySensorDevice): """Represent a single binary sensor in a Netatmo Camera device.""" - def __init__(self, data, camera_name, module_name, home, - timeout, camera_type, sensor): + def __init__( + self, data, camera_name, module_name, home, timeout, camera_type, sensor + ): """Set up for access to the Netatmo camera events.""" self._data = data self._camera_name = camera_name @@ -122,13 +158,13 @@ class NetatmoBinarySensor(BinarySensorDevice): self._home = home self._timeout = timeout if home: - self._name = '{} / {}'.format(home, camera_name) + self._name = "{} / {}".format(home, camera_name) else: self._name = camera_name if module_name: - self._name += ' / ' + module_name + self._name += " / " + module_name self._sensor_name = sensor - self._name += ' ' + sensor + self._name += " " + sensor self._cameratype = camera_type self._state = None @@ -140,9 +176,9 @@ class NetatmoBinarySensor(BinarySensorDevice): @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - if self._cameratype == 'NACamera': + if self._cameratype == "NACamera": return WELCOME_SENSOR_TYPES.get(self._sensor_name) - if self._cameratype == 'NOC': + if self._cameratype == "NOC": return PRESENCE_SENSOR_TYPES.get(self._sensor_name) return TAG_SENSOR_TYPES.get(self._sensor_name) @@ -156,43 +192,41 @@ class NetatmoBinarySensor(BinarySensorDevice): self._data.update() self._data.update_event() - if self._cameratype == 'NACamera': + if self._cameratype == "NACamera": if self._sensor_name == "Someone known": - self._state =\ - self._data.camera_data.someoneKnownSeen( - self._home, self._camera_name, self._timeout) + self._state = self._data.camera_data.someoneKnownSeen( + self._home, self._camera_name, self._timeout + ) elif self._sensor_name == "Someone unknown": - self._state =\ - self._data.camera_data.someoneUnknownSeen( - self._home, self._camera_name, self._timeout) + self._state = self._data.camera_data.someoneUnknownSeen( + self._home, self._camera_name, self._timeout + ) elif self._sensor_name == "Motion": - self._state =\ - self._data.camera_data.motionDetected( - self._home, self._camera_name, self._timeout) - elif self._cameratype == 'NOC': + self._state = self._data.camera_data.motionDetected( + self._home, self._camera_name, self._timeout + ) + elif self._cameratype == "NOC": if self._sensor_name == "Outdoor motion": - self._state =\ - self._data.camera_data.outdoormotionDetected( - self._home, self._camera_name, self._timeout) + self._state = self._data.camera_data.outdoormotionDetected( + self._home, self._camera_name, self._timeout + ) elif self._sensor_name == "Outdoor human": - self._state =\ - self._data.camera_data.humanDetected( - self._home, self._camera_name, self._timeout) + self._state = self._data.camera_data.humanDetected( + self._home, self._camera_name, self._timeout + ) elif self._sensor_name == "Outdoor animal": - self._state =\ - self._data.camera_data.animalDetected( - self._home, self._camera_name, self._timeout) + self._state = self._data.camera_data.animalDetected( + self._home, self._camera_name, self._timeout + ) elif self._sensor_name == "Outdoor vehicle": - self._state =\ - self._data.camera_data.carDetected( - self._home, self._camera_name, self._timeout) + self._state = self._data.camera_data.carDetected( + self._home, self._camera_name, self._timeout + ) if self._sensor_name == "Tag Vibration": - self._state =\ - self._data.camera_data.moduleMotionDetected( - self._home, self._module_name, self._camera_name, - self._timeout) + self._state = self._data.camera_data.moduleMotionDetected( + self._home, self._module_name, self._camera_name, self._timeout + ) elif self._sensor_name == "Tag Open": - self._state =\ - self._data.camera_data.moduleOpened( - self._home, self._module_name, self._camera_name, - self._timeout) + self._state = self._data.camera_data.moduleOpened( + self._home, self._module_name, self._camera_name, self._timeout + ) diff --git a/homeassistant/components/binary_sensor/nx584.py b/homeassistant/components/binary_sensor/nx584.py index 2929acc27..ac53be1d2 100644 --- a/homeassistant/components/binary_sensor/nx584.py +++ b/homeassistant/components/binary_sensor/nx584.py @@ -12,32 +12,36 @@ import requests import voluptuous as vol from homeassistant.components.binary_sensor import ( - DEVICE_CLASSES, BinarySensorDevice, PLATFORM_SCHEMA) -from homeassistant.const import (CONF_HOST, CONF_PORT) + DEVICE_CLASSES, + BinarySensorDevice, + PLATFORM_SCHEMA, +) +from homeassistant.const import CONF_HOST, CONF_PORT import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pynx584==0.4'] +REQUIREMENTS = ["pynx584==0.4"] _LOGGER = logging.getLogger(__name__) -CONF_EXCLUDE_ZONES = 'exclude_zones' -CONF_ZONE_TYPES = 'zone_types' +CONF_EXCLUDE_ZONES = "exclude_zones" +CONF_ZONE_TYPES = "zone_types" -DEFAULT_HOST = 'localhost' -DEFAULT_PORT = '5007' +DEFAULT_HOST = "localhost" +DEFAULT_PORT = "5007" DEFAULT_SSL = False -ZONE_TYPES_SCHEMA = vol.Schema({ - cv.positive_int: vol.In(DEVICE_CLASSES), -}) +ZONE_TYPES_SCHEMA = vol.Schema({cv.positive_int: vol.In(DEVICE_CLASSES)}) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_EXCLUDE_ZONES, default=[]): - vol.All(cv.ensure_list, [cv.positive_int]), - vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_EXCLUDE_ZONES, default=[]): vol.All( + cv.ensure_list, [cv.positive_int] + ), + vol.Optional(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_ZONE_TYPES, default={}): ZONE_TYPES_SCHEMA, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -50,23 +54,22 @@ def setup_platform(hass, config, add_entities, discovery_info=None): zone_types = config.get(CONF_ZONE_TYPES) try: - client = nx584_client.Client('http://{}:{}'.format(host, port)) + client = nx584_client.Client("http://{}:{}".format(host, port)) zones = client.list_zones() except requests.exceptions.ConnectionError as ex: _LOGGER.error("Unable to connect to NX584: %s", str(ex)) return False - version = [int(v) for v in client.get_version().split('.')] + version = [int(v) for v in client.get_version().split(".")] if version < [1, 1]: _LOGGER.error("NX584 is too old to use for sensors (>=0.2 required)") return False zone_sensors = { - zone['number']: NX584ZoneSensor( - zone, - zone_types.get(zone['number'], 'opening')) + zone["number"]: NX584ZoneSensor(zone, zone_types.get(zone["number"], "opening")) for zone in zones - if zone['number'] not in exclude} + if zone["number"] not in exclude + } if zone_sensors: add_entities(zone_sensors.values()) watcher = NX584Watcher(client, zone_sensors) @@ -97,13 +100,13 @@ class NX584ZoneSensor(BinarySensorDevice): @property def name(self): """Return the name of the binary sensor.""" - return self._zone['name'] + return self._zone["name"] @property def is_on(self): """Return true if the binary sensor is on.""" # True means "faulted" or "open" or "abnormal state" - return self._zone['state'] + return self._zone["state"] class NX584Watcher(threading.Thread): @@ -117,17 +120,17 @@ class NX584Watcher(threading.Thread): self._zone_sensors = zone_sensors def _process_zone_event(self, event): - zone = event['zone'] + zone = event["zone"] zone_sensor = self._zone_sensors.get(zone) # pylint: disable=protected-access if not zone_sensor: return - zone_sensor._zone['state'] = event['zone_state'] + zone_sensor._zone["state"] = event["zone_state"] zone_sensor.schedule_update_ha_state() def _process_events(self, events): for event in events: - if event.get('type') == 'zone_status': + if event.get("type") == "zone_status": self._process_zone_event(event) def _run(self): diff --git a/homeassistant/components/binary_sensor/octoprint.py b/homeassistant/components/binary_sensor/octoprint.py index 3dd1ee2be..41af309e3 100644 --- a/homeassistant/components/binary_sensor/octoprint.py +++ b/homeassistant/components/binary_sensor/octoprint.py @@ -10,42 +10,49 @@ import requests import voluptuous as vol from homeassistant.const import CONF_NAME, CONF_MONITORED_CONDITIONS -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['octoprint'] +DEPENDENCIES = ["octoprint"] DOMAIN = "octoprint" -DEFAULT_NAME = 'OctoPrint' +DEFAULT_NAME = "OctoPrint" SENSOR_TYPES = { # API Endpoint, Group, Key, unit - 'Printing': ['printer', 'state', 'printing', None], - 'Printing Error': ['printer', 'state', 'error', None] + "Printing": ["printer", "state", "printing", None], + "Printing Error": ["printer", "state", "error", None], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the available OctoPrint binary sensors.""" octoprint_api = hass.data[DOMAIN]["api"] name = config.get(CONF_NAME) - monitored_conditions = config.get( - CONF_MONITORED_CONDITIONS, SENSOR_TYPES.keys()) + monitored_conditions = config.get(CONF_MONITORED_CONDITIONS, SENSOR_TYPES.keys()) devices = [] for octo_type in monitored_conditions: new_sensor = OctoPrintBinarySensor( - octoprint_api, octo_type, SENSOR_TYPES[octo_type][2], - name, SENSOR_TYPES[octo_type][3], SENSOR_TYPES[octo_type][0], - SENSOR_TYPES[octo_type][1], 'flags') + octoprint_api, + octo_type, + SENSOR_TYPES[octo_type][2], + name, + SENSOR_TYPES[octo_type][3], + SENSOR_TYPES[octo_type][0], + SENSOR_TYPES[octo_type][1], + "flags", + ) devices.append(new_sensor) add_entities(devices, True) @@ -53,14 +60,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class OctoPrintBinarySensor(BinarySensorDevice): """Representation an OctoPrint binary sensor.""" - def __init__(self, api, condition, sensor_type, sensor_name, unit, - endpoint, group, tool=None): + def __init__( + self, api, condition, sensor_type, sensor_name, unit, endpoint, group, tool=None + ): """Initialize a new OctoPrint sensor.""" self.sensor_name = sensor_name if tool is None: - self._name = '{} {}'.format(sensor_name, condition) + self._name = "{} {}".format(sensor_name, condition) else: - self._name = '{} {}'.format(sensor_name, condition) + self._name = "{} {}".format(sensor_name, condition) self.sensor_type = sensor_type self.api = api self._state = False @@ -89,8 +97,8 @@ class OctoPrintBinarySensor(BinarySensorDevice): """Update state of sensor.""" try: self._state = self.api.update( - self.sensor_type, self.api_endpoint, self.api_group, - self.api_tool) + self.sensor_type, self.api_endpoint, self.api_group, self.api_tool + ) except requests.exceptions.ConnectionError: # Error calling the api, already logged in api.update() return diff --git a/homeassistant/components/binary_sensor/openuv.py b/homeassistant/components/binary_sensor/openuv.py index c7c27d73e..cde460c2f 100644 --- a/homeassistant/components/binary_sensor/openuv.py +++ b/homeassistant/components/binary_sensor/openuv.py @@ -10,21 +10,26 @@ from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.components.openuv import ( - BINARY_SENSORS, DATA_OPENUV_CLIENT, 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'] +DEPENDENCIES = ["openuv"] _LOGGER = logging.getLogger(__name__) -ATTR_PROTECTION_WINDOW_STARTING_TIME = 'start_time' -ATTR_PROTECTION_WINDOW_STARTING_UV = 'start_uv' -ATTR_PROTECTION_WINDOW_ENDING_TIME = 'end_time' -ATTR_PROTECTION_WINDOW_ENDING_UV = 'end_uv' +ATTR_PROTECTION_WINDOW_STARTING_TIME = "start_time" +ATTR_PROTECTION_WINDOW_STARTING_UV = "start_uv" +ATTR_PROTECTION_WINDOW_ENDING_TIME = "end_time" +ATTR_PROTECTION_WINDOW_ENDING_UV = "end_uv" -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 an OpenUV sensor based on existing config.""" pass @@ -37,8 +42,8 @@ async def async_setup_entry(hass, entry, async_add_entities): for sensor_type in openuv.binary_sensor_conditions: name, icon = BINARY_SENSORS[sensor_type] binary_sensors.append( - OpenUvBinarySensor( - openuv, sensor_type, name, icon, entry.entry_id)) + OpenUvBinarySensor(openuv, sensor_type, name, icon, entry.entry_id) + ) async_add_entities(binary_sensors, True) @@ -77,8 +82,7 @@ class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice): @property def unique_id(self) -> str: """Return a unique, HASS-friendly identifier for this entity.""" - return '{0}_{1}_{2}'.format( - self._latitude, self._longitude, self._sensor_type) + return "{0}_{1}_{2}".format(self._latitude, self._longitude, self._sensor_type) @callback def _update_data(self): @@ -88,21 +92,28 @@ class OpenUvBinarySensor(OpenUvEntity, BinarySensorDevice): async def async_added_to_hass(self): """Register callbacks.""" self._dispatch_remove = async_dispatcher_connect( - self.hass, TOPIC_UPDATE, self._update_data) + self.hass, TOPIC_UPDATE, self._update_data + ) self.async_on_remove(self._dispatch_remove) async def async_update(self): """Update the state.""" - data = self.openuv.data[DATA_PROTECTION_WINDOW]['result'] + data = self.openuv.data[DATA_PROTECTION_WINDOW]["result"] if self._sensor_type == TYPE_PROTECTION_WINDOW: - self._state = parse_datetime( - data['from_time']) <= utcnow() <= parse_datetime( - data['to_time']) - self._attrs.update({ - ATTR_PROTECTION_WINDOW_ENDING_TIME: - as_local(parse_datetime(data['to_time'])), - ATTR_PROTECTION_WINDOW_ENDING_UV: data['to_uv'], - ATTR_PROTECTION_WINDOW_STARTING_UV: data['from_uv'], - ATTR_PROTECTION_WINDOW_STARTING_TIME: - as_local(parse_datetime(data['from_time'])), - }) + self._state = ( + parse_datetime(data["from_time"]) + <= utcnow() + <= parse_datetime(data["to_time"]) + ) + self._attrs.update( + { + ATTR_PROTECTION_WINDOW_ENDING_TIME: as_local( + parse_datetime(data["to_time"]) + ), + ATTR_PROTECTION_WINDOW_ENDING_UV: data["to_uv"], + ATTR_PROTECTION_WINDOW_STARTING_UV: data["from_uv"], + ATTR_PROTECTION_WINDOW_STARTING_TIME: as_local( + parse_datetime(data["from_time"]) + ), + } + ) diff --git a/homeassistant/components/binary_sensor/pilight.py b/homeassistant/components/binary_sensor/pilight.py index abffffe86..8efe8ab8f 100644 --- a/homeassistant/components/binary_sensor/pilight.py +++ b/homeassistant/components/binary_sensor/pilight.py @@ -9,16 +9,13 @@ import logging import voluptuous as vol from homeassistant.components import pilight -from homeassistant.components.binary_sensor import ( - PLATFORM_SCHEMA, - BinarySensorDevice, -) +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import ( CONF_DISARM_AFTER_TRIGGER, CONF_NAME, CONF_PAYLOAD, CONF_PAYLOAD_OFF, - CONF_PAYLOAD_ON + CONF_PAYLOAD_ON, ) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.event import track_point_in_time @@ -27,45 +24,55 @@ from homeassistant.util import dt as dt_util _LOGGER = logging.getLogger(__name__) -CONF_VARIABLE = 'variable' -CONF_RESET_DELAY_SEC = 'reset_delay_sec' +CONF_VARIABLE = "variable" +CONF_RESET_DELAY_SEC = "reset_delay_sec" -DEFAULT_NAME = 'Pilight Binary Sensor' -DEPENDENCIES = ['pilight'] +DEFAULT_NAME = "Pilight Binary Sensor" +DEPENDENCIES = ["pilight"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_VARIABLE): cv.string, - vol.Required(CONF_PAYLOAD): vol.Schema(dict), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PAYLOAD_ON, default='on'): cv.string, - vol.Optional(CONF_PAYLOAD_OFF, default='off'): cv.string, - vol.Optional(CONF_DISARM_AFTER_TRIGGER, default=False): cv.boolean, - vol.Optional(CONF_RESET_DELAY_SEC, default=30): cv.positive_int -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_VARIABLE): cv.string, + vol.Required(CONF_PAYLOAD): vol.Schema(dict), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_ON, default="on"): cv.string, + vol.Optional(CONF_PAYLOAD_OFF, default="off"): cv.string, + vol.Optional(CONF_DISARM_AFTER_TRIGGER, default=False): cv.boolean, + vol.Optional(CONF_RESET_DELAY_SEC, default=30): cv.positive_int, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up Pilight Binary Sensor.""" disarm = config.get(CONF_DISARM_AFTER_TRIGGER) if disarm: - add_entities([PilightTriggerSensor( - hass=hass, - name=config.get(CONF_NAME), - variable=config.get(CONF_VARIABLE), - payload=config.get(CONF_PAYLOAD), - on_value=config.get(CONF_PAYLOAD_ON), - off_value=config.get(CONF_PAYLOAD_OFF), - rst_dly_sec=config.get(CONF_RESET_DELAY_SEC), - )]) + add_entities( + [ + PilightTriggerSensor( + hass=hass, + name=config.get(CONF_NAME), + variable=config.get(CONF_VARIABLE), + payload=config.get(CONF_PAYLOAD), + on_value=config.get(CONF_PAYLOAD_ON), + off_value=config.get(CONF_PAYLOAD_OFF), + rst_dly_sec=config.get(CONF_RESET_DELAY_SEC), + ) + ] + ) else: - add_entities([PilightBinarySensor( - hass=hass, - name=config.get(CONF_NAME), - variable=config.get(CONF_VARIABLE), - payload=config.get(CONF_PAYLOAD), - on_value=config.get(CONF_PAYLOAD_ON), - off_value=config.get(CONF_PAYLOAD_OFF), - )]) + add_entities( + [ + PilightBinarySensor( + hass=hass, + name=config.get(CONF_NAME), + variable=config.get(CONF_VARIABLE), + payload=config.get(CONF_PAYLOAD), + on_value=config.get(CONF_PAYLOAD_ON), + off_value=config.get(CONF_PAYLOAD_OFF), + ) + ] + ) class PilightBinarySensor(BinarySensorDevice): @@ -113,7 +120,7 @@ class PilightBinarySensor(BinarySensorDevice): if self._variable not in call.data: return value = call.data[self._variable] - self._state = (value == self._on_value) + self._state = value == self._on_value self.schedule_update_ha_state() @@ -121,14 +128,8 @@ class PilightTriggerSensor(BinarySensorDevice): """Representation of a binary sensor that can be updated using Pilight.""" def __init__( - self, - hass, - name, - variable, - payload, - on_value, - off_value, - rst_dly_sec=30): + self, hass, name, variable, payload, on_value, off_value, rst_dly_sec=30 + ): """Initialize the sensor.""" self._state = False self._hass = hass @@ -178,11 +179,10 @@ class PilightTriggerSensor(BinarySensorDevice): if self._variable not in call.data: return value = call.data[self._variable] - self._state = (value == self._on_value) + self._state = value == self._on_value if self._delay_after is None: self._delay_after = dt_util.utcnow() + datetime.timedelta( - seconds=self._reset_delay_sec) - track_point_in_time( - self._hass, self._reset_state, - self._delay_after) + seconds=self._reset_delay_sec + ) + track_point_in_time(self._hass, self._reset_state, self._delay_after) self.schedule_update_ha_state() diff --git a/homeassistant/components/binary_sensor/ping.py b/homeassistant/components/binary_sensor/ping.py index 4c597dd63..52c1ebc03 100644 --- a/homeassistant/components/binary_sensor/ping.py +++ b/homeassistant/components/binary_sensor/ping.py @@ -13,39 +13,41 @@ from datetime import timedelta import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_NAME, CONF_HOST _LOGGER = logging.getLogger(__name__) -ATTR_ROUND_TRIP_TIME_AVG = 'round_trip_time_avg' -ATTR_ROUND_TRIP_TIME_MAX = 'round_trip_time_max' -ATTR_ROUND_TRIP_TIME_MDEV = 'round_trip_time_mdev' -ATTR_ROUND_TRIP_TIME_MIN = 'round_trip_time_min' +ATTR_ROUND_TRIP_TIME_AVG = "round_trip_time_avg" +ATTR_ROUND_TRIP_TIME_MAX = "round_trip_time_max" +ATTR_ROUND_TRIP_TIME_MDEV = "round_trip_time_mdev" +ATTR_ROUND_TRIP_TIME_MIN = "round_trip_time_min" -CONF_PING_COUNT = 'count' +CONF_PING_COUNT = "count" -DEFAULT_NAME = 'Ping Binary sensor' +DEFAULT_NAME = "Ping Binary sensor" DEFAULT_PING_COUNT = 5 -DEFAULT_DEVICE_CLASS = 'connectivity' +DEFAULT_DEVICE_CLASS = "connectivity" SCAN_INTERVAL = timedelta(minutes=5) PING_MATCHER = re.compile( - r'(?P\d+.\d+)\/(?P\d+.\d+)\/(?P\d+.\d+)\/(?P\d+.\d+)') + r"(?P\d+.\d+)\/(?P\d+.\d+)\/(?P\d+.\d+)\/(?P\d+.\d+)" +) PING_MATCHER_BUSYBOX = re.compile( - r'(?P\d+.\d+)\/(?P\d+.\d+)\/(?P\d+.\d+)') + r"(?P\d+.\d+)\/(?P\d+.\d+)\/(?P\d+.\d+)" +) -WIN32_PING_MATCHER = re.compile( - r'(?P\d+)ms.+(?P\d+)ms.+(?P\d+)ms') +WIN32_PING_MATCHER = re.compile(r"(?P\d+)ms.+(?P\d+)ms.+(?P\d+)ms") -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PING_COUNT, default=DEFAULT_PING_COUNT): cv.positive_int, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PING_COUNT, default=DEFAULT_PING_COUNT): cv.positive_int, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -85,10 +87,10 @@ class PingBinarySensor(BinarySensorDevice): """Return the state attributes of the ICMP checo request.""" if self.ping.data is not False: return { - ATTR_ROUND_TRIP_TIME_AVG: self.ping.data['avg'], - ATTR_ROUND_TRIP_TIME_MAX: self.ping.data['max'], - ATTR_ROUND_TRIP_TIME_MDEV: self.ping.data['mdev'], - ATTR_ROUND_TRIP_TIME_MIN: self.ping.data['min'], + ATTR_ROUND_TRIP_TIME_AVG: self.ping.data["avg"], + ATTR_ROUND_TRIP_TIME_MAX: self.ping.data["max"], + ATTR_ROUND_TRIP_TIME_MDEV: self.ping.data["mdev"], + ATTR_ROUND_TRIP_TIME_MIN: self.ping.data["min"], } def update(self): @@ -106,44 +108,45 @@ class PingData: self.data = {} self.available = False - if sys.platform == 'win32': + if sys.platform == "win32": self._ping_cmd = [ - 'ping', '-n', str(self._count), '-w', '1000', self._ip_address] + "ping", + "-n", + str(self._count), + "-w", + "1000", + self._ip_address, + ] else: self._ping_cmd = [ - 'ping', '-n', '-q', '-c', str(self._count), '-W1', - self._ip_address] + "ping", + "-n", + "-q", + "-c", + str(self._count), + "-W1", + self._ip_address, + ] def ping(self): """Send ICMP echo request and return details if success.""" pinger = subprocess.Popen( - self._ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + self._ping_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) try: out = pinger.communicate() _LOGGER.debug("Output is %s", str(out)) - if sys.platform == 'win32': - match = WIN32_PING_MATCHER.search(str(out).split('\n')[-1]) + if sys.platform == "win32": + match = WIN32_PING_MATCHER.search(str(out).split("\n")[-1]) rtt_min, rtt_avg, rtt_max = match.groups() - return { - 'min': rtt_min, - 'avg': rtt_avg, - 'max': rtt_max, - 'mdev': ''} - if 'max/' not in str(out): - match = PING_MATCHER_BUSYBOX.search(str(out).split('\n')[-1]) + return {"min": rtt_min, "avg": rtt_avg, "max": rtt_max, "mdev": ""} + if "max/" not in str(out): + match = PING_MATCHER_BUSYBOX.search(str(out).split("\n")[-1]) rtt_min, rtt_avg, rtt_max = match.groups() - return { - 'min': rtt_min, - 'avg': rtt_avg, - 'max': rtt_max, - 'mdev': ''} - match = PING_MATCHER.search(str(out).split('\n')[-1]) + return {"min": rtt_min, "avg": rtt_avg, "max": rtt_max, "mdev": ""} + match = PING_MATCHER.search(str(out).split("\n")[-1]) rtt_min, rtt_avg, rtt_max, rtt_mdev = match.groups() - return { - 'min': rtt_min, - 'avg': rtt_avg, - 'max': rtt_max, - 'mdev': rtt_mdev} + return {"min": rtt_min, "avg": rtt_avg, "max": rtt_max, "mdev": rtt_mdev} except (subprocess.CalledProcessError, AttributeError): return False diff --git a/homeassistant/components/binary_sensor/qwikswitch.py b/homeassistant/components/binary_sensor/qwikswitch.py index 2fe14773d..619bc9d2d 100644 --- a/homeassistant/components/binary_sensor/qwikswitch.py +++ b/homeassistant/components/binary_sensor/qwikswitch.py @@ -21,8 +21,7 @@ async def async_setup_platform(hass, _, add_entities, discovery_info=None): return qsusb = hass.data[QWIKSWITCH] - _LOGGER.debug("Setup qwikswitch.binary_sensor %s, %s", - qsusb, discovery_info) + _LOGGER.debug("Setup qwikswitch.binary_sensor %s, %s", qsusb, discovery_info) devs = [QSBinarySensor(sensor) for sensor in discovery_info[QWIKSWITCH]] add_entities(devs) @@ -36,20 +35,26 @@ class QSBinarySensor(QSEntity, BinarySensorDevice): """Initialize the sensor.""" from pyqwikswitch import SENSORS - super().__init__(sensor['id'], sensor['name']) - self.channel = sensor['channel'] - sensor_type = sensor['type'] + super().__init__(sensor["id"], sensor["name"]) + self.channel = sensor["channel"] + sensor_type = sensor["type"] self._decode, _ = SENSORS[sensor_type] - self._invert = not sensor.get('invert', False) - self._class = sensor.get('class', 'door') + self._invert = not sensor.get("invert", False) + self._class = sensor.get("class", "door") @callback def update_packet(self, packet): """Receive update packet from QSUSB.""" val = self._decode(packet, channel=self.channel) - _LOGGER.debug("Update %s (%s:%s) decoded as %s: %s", - self.entity_id, self.qsid, self.channel, val, packet) + _LOGGER.debug( + "Update %s (%s:%s) decoded as %s: %s", + self.entity_id, + self.qsid, + self.channel, + val, + packet, + ) if val is not None: self._val = bool(val) self.async_schedule_update_ha_state() diff --git a/homeassistant/components/binary_sensor/rachio.py b/homeassistant/components/binary_sensor/rachio.py index 798b6a754..613dcd1e0 100644 --- a/homeassistant/components/binary_sensor/rachio.py +++ b/homeassistant/components/binary_sensor/rachio.py @@ -8,18 +8,20 @@ from abc import abstractmethod import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.rachio import (DOMAIN as DOMAIN_RACHIO, - KEY_DEVICE_ID, - KEY_STATUS, - KEY_SUBTYPE, - SIGNAL_RACHIO_CONTROLLER_UPDATE, - STATUS_OFFLINE, - STATUS_ONLINE, - SUBTYPE_OFFLINE, - SUBTYPE_ONLINE,) +from homeassistant.components.rachio import ( + DOMAIN as DOMAIN_RACHIO, + KEY_DEVICE_ID, + KEY_STATUS, + KEY_SUBTYPE, + SIGNAL_RACHIO_CONTROLLER_UPDATE, + STATUS_OFFLINE, + STATUS_ONLINE, + SUBTYPE_OFFLINE, + SUBTYPE_ONLINE, +) from homeassistant.helpers.dispatcher import dispatcher_connect -DEPENDENCIES = ['rachio'] +DEPENDENCIES = ["rachio"] _LOGGER = logging.getLogger(__name__) @@ -46,8 +48,9 @@ class RachioControllerBinarySensor(BinarySensorDevice): else: self._state = None - dispatcher_connect(hass, SIGNAL_RACHIO_CONTROLLER_UPDATE, - self._handle_any_update) + dispatcher_connect( + hass, SIGNAL_RACHIO_CONTROLLER_UPDATE, self._handle_any_update + ) @property def should_poll(self) -> bool: @@ -95,26 +98,25 @@ class RachioControllerOnlineBinarySensor(RachioControllerBinarySensor): @property def device_class(self) -> str: """Return the class of this device, from component DEVICE_CLASSES.""" - return 'connectivity' + return "connectivity" @property def icon(self) -> str: """Return the name of an icon for this sensor.""" - return 'mdi:wifi-strength-4' if self.is_on\ - else 'mdi:wifi-strength-off-outline' + return "mdi:wifi-strength-4" if self.is_on else "mdi:wifi-strength-off-outline" def _poll_update(self, data=None) -> bool: """Request the state from the API.""" if data is None: - data = self._controller.rachio.device.get( - self._controller.controller_id)[1] + data = self._controller.rachio.device.get(self._controller.controller_id)[1] if data[KEY_STATUS] == STATUS_ONLINE: return True if data[KEY_STATUS] == STATUS_OFFLINE: return False - _LOGGER.warning('"%s" reported in unknown state "%s"', self.name, - data[KEY_STATUS]) + _LOGGER.warning( + '"%s" reported in unknown state "%s"', self.name, data[KEY_STATUS] + ) def _handle_update(self, *args, **kwargs) -> None: """Handle an update to the state of this sensor.""" diff --git a/homeassistant/components/binary_sensor/raincloud.py b/homeassistant/components/binary_sensor/raincloud.py index 810c7d201..c77d3a656 100644 --- a/homeassistant/components/binary_sensor/raincloud.py +++ b/homeassistant/components/binary_sensor/raincloud.py @@ -10,19 +10,25 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.raincloud import ( - BINARY_SENSORS, DATA_RAINCLOUD, ICON_MAP, RainCloudEntity) -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) + BINARY_SENSORS, + DATA_RAINCLOUD, + ICON_MAP, + RainCloudEntity, +) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_MONITORED_CONDITIONS -DEPENDENCIES = ['raincloud'] +DEPENDENCIES = ["raincloud"] _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_MONITORED_CONDITIONS, default=list(BINARY_SENSORS)): - vol.All(cv.ensure_list, [vol.In(BINARY_SENSORS)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_MONITORED_CONDITIONS, default=list(BINARY_SENSORS)): vol.All( + cv.ensure_list, [vol.In(BINARY_SENSORS)] + ) + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -31,12 +37,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensors = [] for sensor_type in config.get(CONF_MONITORED_CONDITIONS): - if sensor_type == 'status': + if sensor_type == "status": + sensors.append(RainCloudBinarySensor(raincloud.controller, sensor_type)) sensors.append( - RainCloudBinarySensor(raincloud.controller, sensor_type)) - sensors.append( - RainCloudBinarySensor(raincloud.controller.faucet, - sensor_type)) + RainCloudBinarySensor(raincloud.controller.faucet, sensor_type) + ) else: # create a sensor for each zone managed by faucet @@ -59,14 +64,14 @@ class RainCloudBinarySensor(RainCloudEntity, BinarySensorDevice): """Get the latest data and updates the state.""" _LOGGER.debug("Updating RainCloud sensor: %s", self._name) self._state = getattr(self.data, self._sensor_type) - if self._sensor_type == 'status': - self._state = self._state == 'Online' + if self._sensor_type == "status": + self._state = self._state == "Online" @property def icon(self): """Return the icon of this device.""" - if self._sensor_type == 'is_watering': - return 'mdi:water' if self.is_on else 'mdi:water-off' - if self._sensor_type == 'status': - return 'mdi:pipe' if self.is_on else 'mdi:pipe-disconnected' + if self._sensor_type == "is_watering": + return "mdi:water" if self.is_on else "mdi:water-off" + if self._sensor_type == "status": + return "mdi:pipe" if self.is_on else "mdi:pipe-disconnected" return ICON_MAP.get(self._sensor_type) diff --git a/homeassistant/components/binary_sensor/rainmachine.py b/homeassistant/components/binary_sensor/rainmachine.py index 12c9b3e98..42a7bd03e 100644 --- a/homeassistant/components/binary_sensor/rainmachine.py +++ b/homeassistant/components/binary_sensor/rainmachine.py @@ -8,20 +8,29 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.rainmachine import ( - BINARY_SENSORS, DATA_RAINMACHINE, SENSOR_UPDATE_TOPIC, TYPE_FREEZE, - TYPE_FREEZE_PROTECTION, TYPE_HOT_DAYS, TYPE_HOURLY, TYPE_MONTH, - TYPE_RAINDELAY, TYPE_RAINSENSOR, TYPE_WEEKDAY, RainMachineEntity) + BINARY_SENSORS, + DATA_RAINMACHINE, + SENSOR_UPDATE_TOPIC, + TYPE_FREEZE, + TYPE_FREEZE_PROTECTION, + TYPE_HOT_DAYS, + TYPE_HOURLY, + TYPE_MONTH, + TYPE_RAINDELAY, + TYPE_RAINSENSOR, + TYPE_WEEKDAY, + RainMachineEntity, +) from homeassistant.const import CONF_MONITORED_CONDITIONS from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -DEPENDENCIES = ['rainmachine'] +DEPENDENCIES = ["rainmachine"] _LOGGER = logging.getLogger(__name__) -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 RainMachine Switch platform.""" if discovery_info is None: return @@ -32,7 +41,8 @@ async def async_setup_platform( for sensor_type in discovery_info[CONF_MONITORED_CONDITIONS]: name, icon = BINARY_SENSORS[sensor_type] binary_sensors.append( - RainMachineBinarySensor(rainmachine, sensor_type, name, icon)) + RainMachineBinarySensor(rainmachine, sensor_type, name, icon) + ) async_add_entities(binary_sensors, True) @@ -67,8 +77,9 @@ class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice): @property def unique_id(self) -> str: """Return a unique, HASS-friendly identifier for this entity.""" - return '{0}_{1}'.format( - self.rainmachine.device_mac.replace(':', ''), self._sensor_type) + return "{0}_{1}".format( + self.rainmachine.device_mac.replace(":", ""), self._sensor_type + ) @callback def _update_data(self): @@ -77,27 +88,27 @@ class RainMachineBinarySensor(RainMachineEntity, BinarySensorDevice): async def async_added_to_hass(self): """Register callbacks.""" - async_dispatcher_connect( - self.hass, SENSOR_UPDATE_TOPIC, self._update_data) + async_dispatcher_connect(self.hass, SENSOR_UPDATE_TOPIC, self._update_data) async def async_update(self): """Update the state.""" if self._sensor_type == TYPE_FREEZE: - self._state = self.rainmachine.restrictions['current']['freeze'] + self._state = self.rainmachine.restrictions["current"]["freeze"] elif self._sensor_type == TYPE_FREEZE_PROTECTION: - self._state = self.rainmachine.restrictions['global'][ - 'freezeProtectEnabled'] + self._state = self.rainmachine.restrictions["global"][ + "freezeProtectEnabled" + ] elif self._sensor_type == TYPE_HOT_DAYS: - self._state = self.rainmachine.restrictions['global'][ - 'hotDaysExtraWatering'] + self._state = self.rainmachine.restrictions["global"][ + "hotDaysExtraWatering" + ] elif self._sensor_type == TYPE_HOURLY: - self._state = self.rainmachine.restrictions['current']['hourly'] + self._state = self.rainmachine.restrictions["current"]["hourly"] elif self._sensor_type == TYPE_MONTH: - self._state = self.rainmachine.restrictions['current']['month'] + self._state = self.rainmachine.restrictions["current"]["month"] elif self._sensor_type == TYPE_RAINDELAY: - self._state = self.rainmachine.restrictions['current']['rainDelay'] + self._state = self.rainmachine.restrictions["current"]["rainDelay"] elif self._sensor_type == TYPE_RAINSENSOR: - self._state = self.rainmachine.restrictions['current'][ - 'rainSensor'] + self._state = self.rainmachine.restrictions["current"]["rainSensor"] elif self._sensor_type == TYPE_WEEKDAY: - self._state = self.rainmachine.restrictions['current']['weekDay'] + self._state = self.rainmachine.restrictions["current"]["weekDay"] diff --git a/homeassistant/components/binary_sensor/random.py b/homeassistant/components/binary_sensor/random.py index 9bdc57c6e..f461122cd 100644 --- a/homeassistant/components/binary_sensor/random.py +++ b/homeassistant/components/binary_sensor/random.py @@ -10,21 +10,25 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA, DEVICE_CLASSES_SCHEMA) + BinarySensorDevice, + PLATFORM_SCHEMA, + DEVICE_CLASSES_SCHEMA, +) from homeassistant.const import CONF_NAME, CONF_DEVICE_CLASS _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Random Binary Sensor' +DEFAULT_NAME = "Random Binary Sensor" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + } +) -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 Random binary sensor.""" name = config.get(CONF_NAME) device_class = config.get(CONF_DEVICE_CLASS) @@ -59,4 +63,5 @@ class RandomSensor(BinarySensorDevice): async def async_update(self): """Get new state and update the sensor's state.""" from random import getrandbits + self._state = bool(getrandbits(1)) diff --git a/homeassistant/components/binary_sensor/raspihats.py b/homeassistant/components/binary_sensor/raspihats.py index feef5396d..e31b650ec 100644 --- a/homeassistant/components/binary_sensor/raspihats.py +++ b/homeassistant/components/binary_sensor/raspihats.py @@ -8,38 +8,52 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import ( - PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.components.raspihats import ( - CONF_ADDRESS, CONF_BOARD, CONF_CHANNELS, CONF_I2C_HATS, CONF_INDEX, - CONF_INVERT_LOGIC, I2C_HAT_NAMES, I2C_HATS_MANAGER, I2CHatsException) -from homeassistant.const import ( - CONF_DEVICE_CLASS, CONF_NAME, DEVICE_DEFAULT_NAME) + CONF_ADDRESS, + CONF_BOARD, + CONF_CHANNELS, + CONF_I2C_HATS, + CONF_INDEX, + CONF_INVERT_LOGIC, + I2C_HAT_NAMES, + I2C_HATS_MANAGER, + I2CHatsException, +) +from homeassistant.const import CONF_DEVICE_CLASS, CONF_NAME, DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['raspihats'] +DEPENDENCIES = ["raspihats"] DEFAULT_INVERT_LOGIC = False DEFAULT_DEVICE_CLASS = None -_CHANNELS_SCHEMA = vol.Schema([{ - vol.Required(CONF_INDEX): cv.positive_int, - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, - vol.Optional(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS): cv.string, -}]) +_CHANNELS_SCHEMA = vol.Schema( + [ + { + vol.Required(CONF_INDEX): cv.positive_int, + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, + vol.Optional(CONF_DEVICE_CLASS, default=DEFAULT_DEVICE_CLASS): cv.string, + } + ] +) -_I2C_HATS_SCHEMA = vol.Schema([{ - vol.Required(CONF_BOARD): vol.In(I2C_HAT_NAMES), - vol.Required(CONF_ADDRESS): vol.Coerce(int), - vol.Required(CONF_CHANNELS): _CHANNELS_SCHEMA -}]) +_I2C_HATS_SCHEMA = vol.Schema( + [ + { + vol.Required(CONF_BOARD): vol.In(I2C_HAT_NAMES), + vol.Required(CONF_ADDRESS): vol.Coerce(int), + vol.Required(CONF_CHANNELS): _CHANNELS_SCHEMA, + } + ] +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_I2C_HATS): _I2C_HATS_SCHEMA, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_I2C_HATS): _I2C_HATS_SCHEMA} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -59,12 +73,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): channel_config[CONF_INDEX], channel_config[CONF_NAME], channel_config[CONF_INVERT_LOGIC], - channel_config[CONF_DEVICE_CLASS] + channel_config[CONF_DEVICE_CLASS], ) ) except I2CHatsException as ex: - _LOGGER.error("Failed to register %s I2CHat@%s %s", - board, hex(address), str(ex)) + _LOGGER.error( + "Failed to register %s I2CHat@%s %s", board, hex(address), str(ex) + ) add_entities(binary_sensors) @@ -80,15 +95,15 @@ class I2CHatBinarySensor(BinarySensorDevice): self._name = name or DEVICE_DEFAULT_NAME self._invert_logic = invert_logic self._device_class = device_class - self._state = self.I2C_HATS_MANAGER.read_di( - self._address, self._channel) + self._state = self.I2C_HATS_MANAGER.read_di(self._address, self._channel) def online_callback(): """Call fired when board is online.""" self.schedule_update_ha_state() self.I2C_HATS_MANAGER.register_online_callback( - self._address, self._channel, online_callback) + self._address, self._channel, online_callback + ) def edge_callback(state): """Read digital input state.""" @@ -96,7 +111,8 @@ class I2CHatBinarySensor(BinarySensorDevice): self.schedule_update_ha_state() self.I2C_HATS_MANAGER.register_di_callback( - self._address, self._channel, edge_callback) + self._address, self._channel, edge_callback + ) @property def device_class(self): diff --git a/homeassistant/components/binary_sensor/rest.py b/homeassistant/components/binary_sensor/rest.py index 412aeb46a..5aa5f3762 100644 --- a/homeassistant/components/binary_sensor/rest.py +++ b/homeassistant/components/binary_sensor/rest.py @@ -10,35 +10,51 @@ import voluptuous as vol from requests.auth import HTTPBasicAuth, HTTPDigestAuth from homeassistant.components.binary_sensor import ( - BinarySensorDevice, DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA) + BinarySensorDevice, + DEVICE_CLASSES_SCHEMA, + PLATFORM_SCHEMA, +) from homeassistant.components.sensor.rest import RestData from homeassistant.const import ( - CONF_PAYLOAD, CONF_NAME, CONF_VALUE_TEMPLATE, CONF_METHOD, CONF_RESOURCE, - CONF_VERIFY_SSL, CONF_USERNAME, CONF_PASSWORD, - CONF_HEADERS, CONF_AUTHENTICATION, HTTP_BASIC_AUTHENTICATION, - HTTP_DIGEST_AUTHENTICATION, CONF_DEVICE_CLASS) + CONF_PAYLOAD, + CONF_NAME, + CONF_VALUE_TEMPLATE, + CONF_METHOD, + CONF_RESOURCE, + CONF_VERIFY_SSL, + CONF_USERNAME, + CONF_PASSWORD, + CONF_HEADERS, + CONF_AUTHENTICATION, + HTTP_BASIC_AUTHENTICATION, + HTTP_DIGEST_AUTHENTICATION, + CONF_DEVICE_CLASS, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEFAULT_METHOD = 'GET' -DEFAULT_NAME = 'REST Binary Sensor' +DEFAULT_METHOD = "GET" +DEFAULT_NAME = "REST Binary Sensor" DEFAULT_VERIFY_SSL = True -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_RESOURCE): cv.url, - vol.Optional(CONF_AUTHENTICATION): - vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]), - vol.Optional(CONF_HEADERS): {cv.string: cv.string}, - vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(['POST', 'GET']), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_PAYLOAD): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_RESOURCE): cv.url, + vol.Optional(CONF_AUTHENTICATION): vol.In( + [HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION] + ), + vol.Optional(CONF_HEADERS): {cv.string: cv.string}, + vol.Optional(CONF_METHOD, default=DEFAULT_METHOD): vol.In(["POST", "GET"]), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_PAYLOAD): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_VERIFY_SSL, default=DEFAULT_VERIFY_SSL): cv.boolean, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -71,8 +87,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error("Unable to fetch REST data from %s", resource) return False - add_entities([RestBinarySensor( - hass, rest, name, device_class, value_template)], True) + add_entities( + [RestBinarySensor(hass, rest, name, device_class, value_template)], True + ) class RestBinarySensor(BinarySensorDevice): @@ -112,14 +129,16 @@ class RestBinarySensor(BinarySensorDevice): response = self.rest.data if self._value_template is not None: - response = self._value_template.\ - async_render_with_possible_json_value(self.rest.data, False) + response = self._value_template.async_render_with_possible_json_value( + self.rest.data, False + ) try: return bool(int(response)) except ValueError: - return {'true': True, 'on': True, 'open': True, - 'yes': True}.get(response.lower(), False) + return {"true": True, "on": True, "open": True, "yes": True}.get( + response.lower(), False + ) def update(self): """Get the latest data from REST API and updates the state.""" diff --git a/homeassistant/components/binary_sensor/rfxtrx.py b/homeassistant/components/binary_sensor/rfxtrx.py index 1e88c72e1..d56377443 100644 --- a/homeassistant/components/binary_sensor/rfxtrx.py +++ b/homeassistant/components/binary_sensor/rfxtrx.py @@ -10,12 +10,24 @@ import voluptuous as vol from homeassistant.components import rfxtrx from homeassistant.components.binary_sensor import ( - DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice) + DEVICE_CLASSES_SCHEMA, + PLATFORM_SCHEMA, + BinarySensorDevice, +) from homeassistant.components.rfxtrx import ( - ATTR_NAME, CONF_AUTOMATIC_ADD, CONF_DATA_BITS, CONF_DEVICES, - CONF_FIRE_EVENT, CONF_OFF_DELAY) + ATTR_NAME, + CONF_AUTOMATIC_ADD, + CONF_DATA_BITS, + CONF_DEVICES, + CONF_FIRE_EVENT, + CONF_OFF_DELAY, +) from homeassistant.const import ( - CONF_COMMAND_OFF, CONF_COMMAND_ON, CONF_DEVICE_CLASS, CONF_NAME) + CONF_COMMAND_OFF, + CONF_COMMAND_ON, + CONF_DEVICE_CLASS, + CONF_NAME, +) from homeassistant.helpers import config_validation as cv from homeassistant.helpers import event as evt from homeassistant.util import dt as dt_util @@ -23,28 +35,35 @@ from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['rfxtrx'] +DEPENDENCIES = ["rfxtrx"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_DEVICES, default={}): { - cv.string: vol.Schema({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, - vol.Optional(CONF_OFF_DELAY): - vol.Any(cv.time_period, cv.positive_timedelta), - vol.Optional(CONF_DATA_BITS): cv.positive_int, - vol.Optional(CONF_COMMAND_ON): cv.byte, - vol.Optional(CONF_COMMAND_OFF): cv.byte - }) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_DEVICES, default={}): { + cv.string: vol.Schema( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, + vol.Optional(CONF_OFF_DELAY): vol.Any( + cv.time_period, cv.positive_timedelta + ), + vol.Optional(CONF_DATA_BITS): cv.positive_int, + vol.Optional(CONF_COMMAND_ON): cv.byte, + vol.Optional(CONF_COMMAND_OFF): cv.byte, + } + ) + }, + vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, }, - vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, -}, extra=vol.ALLOW_EXTRA) + extra=vol.ALLOW_EXTRA, +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Binary Sensor platform to RFXtrx.""" import RFXtrx as rfxtrxmod + sensors = [] for packet_id, entity in config[CONF_DEVICES].items(): @@ -56,17 +75,26 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if entity.get(CONF_DATA_BITS) is not None: _LOGGER.debug( - "Masked device id: %s", rfxtrx.get_pt2262_deviceid( - device_id, entity.get(CONF_DATA_BITS))) + "Masked device id: %s", + rfxtrx.get_pt2262_deviceid(device_id, entity.get(CONF_DATA_BITS)), + ) - _LOGGER.debug("Add %s rfxtrx.binary_sensor (class %s)", - entity[ATTR_NAME], entity.get(CONF_DEVICE_CLASS)) + _LOGGER.debug( + "Add %s rfxtrx.binary_sensor (class %s)", + entity[ATTR_NAME], + entity.get(CONF_DEVICE_CLASS), + ) device = RfxtrxBinarySensor( - event, entity.get(CONF_NAME), entity.get(CONF_DEVICE_CLASS), - entity[CONF_FIRE_EVENT], entity.get(CONF_OFF_DELAY), - entity.get(CONF_DATA_BITS), entity.get(CONF_COMMAND_ON), - entity.get(CONF_COMMAND_OFF)) + event, + entity.get(CONF_NAME), + entity.get(CONF_DEVICE_CLASS), + entity[CONF_FIRE_EVENT], + entity.get(CONF_OFF_DELAY), + entity.get(CONF_DATA_BITS), + entity.get(CONF_COMMAND_ON), + entity.get(CONF_COMMAND_OFF), + ) device.hass = hass sensors.append(device) rfxtrx.RFX_DEVICES[device_id] = device @@ -94,8 +122,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): poss_dev = rfxtrx.find_possible_pt2262_device(device_id) if poss_dev is not None: poss_id = slugify(poss_dev.event.device.id_string.lower()) - _LOGGER.debug( - "Found possible matching device ID: %s", poss_id) + _LOGGER.debug("Found possible matching device ID: %s", poss_id) pkt_id = "".join("{0:02x}".format(x) for x in event.data) sensor = RfxtrxBinarySensor(event, pkt_id) @@ -104,8 +131,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): add_entities([sensor]) _LOGGER.info( "Added binary sensor %s (Device ID: %s Class: %s Sub: %s)", - pkt_id, slugify(event.device.id_string.lower()), - event.device.__class__.__name__, event.device.subtype) + pkt_id, + slugify(event.device.id_string.lower()), + event.device.__class__.__name__, + event.device.subtype, + ) elif not isinstance(sensor, RfxtrxBinarySensor): return @@ -113,7 +143,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.debug( "Binary sensor update (Device ID: %s Class: %s Sub: %s)", slugify(event.device.id_string.lower()), - event.device.__class__.__name__, event.device.subtype) + event.device.__class__.__name__, + event.device.subtype, + ) if sensor.is_lighting4: if sensor.data_bits is not None: @@ -124,8 +156,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): else: rfxtrx.apply_received_command(event) - if (sensor.is_on and sensor.off_delay is not None and - sensor.delay_listener is None): + if ( + sensor.is_on + and sensor.off_delay is not None + and sensor.delay_listener is None + ): def off_delay_listener(now): """Switch device off after a delay.""" @@ -133,7 +168,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensor.update_state(False) sensor.delay_listener = evt.track_point_in_time( - hass, off_delay_listener, dt_util.utcnow() + sensor.off_delay) + hass, off_delay_listener, dt_util.utcnow() + sensor.off_delay + ) # Subscribe to main RFXtrx events if binary_sensor_update not in rfxtrx.RECEIVED_EVT_SUBSCRIBERS: @@ -143,9 +179,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class RfxtrxBinarySensor(BinarySensorDevice): """A representation of a RFXtrx binary sensor.""" - def __init__(self, event, name, device_class=None, - should_fire=False, off_delay=None, data_bits=None, - cmd_on=None, cmd_off=None): + def __init__( + self, + event, + name, + device_class=None, + should_fire=False, + off_delay=None, + data_bits=None, + cmd_on=None, + cmd_off=None, + ): """Initialize the RFXtrx sensor.""" self.event = event self._name = name @@ -153,7 +197,7 @@ class RfxtrxBinarySensor(BinarySensorDevice): self._device_class = device_class self._off_delay = off_delay self._state = False - self.is_lighting4 = (event.device.packettype == 0x13) + self.is_lighting4 = event.device.packettype == 0x13 self.delay_listener = None self._data_bits = data_bits self._cmd_on = cmd_on @@ -161,7 +205,8 @@ class RfxtrxBinarySensor(BinarySensorDevice): if data_bits is not None: self._masked_id = rfxtrx.get_pt2262_deviceid( - event.device.id_string.lower(), data_bits) + event.device.id_string.lower(), data_bits + ) else: self._masked_id = None diff --git a/homeassistant/components/binary_sensor/ring.py b/homeassistant/components/binary_sensor/ring.py index 102e22cbe..ec12230c6 100644 --- a/homeassistant/components/binary_sensor/ring.py +++ b/homeassistant/components/binary_sensor/ring.py @@ -11,15 +11,20 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.ring import ( - CONF_ATTRIBUTION, DEFAULT_ENTITY_NAMESPACE, DATA_RING) + CONF_ATTRIBUTION, + DEFAULT_ENTITY_NAMESPACE, + DATA_RING, +) from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS) + ATTR_ATTRIBUTION, + CONF_ENTITY_NAMESPACE, + CONF_MONITORED_CONDITIONS, +) -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA -DEPENDENCIES = ['ring'] +DEPENDENCIES = ["ring"] _LOGGER = logging.getLogger(__name__) @@ -27,16 +32,20 @@ SCAN_INTERVAL = timedelta(seconds=10) # Sensor types: Name, category, device_class SENSOR_TYPES = { - 'ding': ['Ding', ['doorbell'], 'occupancy'], - 'motion': ['Motion', ['doorbell', 'stickup_cams'], 'motion'], + "ding": ["Ding", ["doorbell"], "occupancy"], + "motion": ["Motion", ["doorbell", "stickup_cams"], "motion"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE): - cv.string, - vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional( + CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE + ): cv.string, + vol.Required(CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES)): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -46,16 +55,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensors = [] for sensor_type in config.get(CONF_MONITORED_CONDITIONS): for device in ring.doorbells: - if 'doorbell' in SENSOR_TYPES[sensor_type][1]: - sensors.append(RingBinarySensor(hass, - device, - sensor_type)) + if "doorbell" in SENSOR_TYPES[sensor_type][1]: + sensors.append(RingBinarySensor(hass, device, sensor_type)) for device in ring.stickup_cams: - if 'stickup_cams' in SENSOR_TYPES[sensor_type][1]: - sensors.append(RingBinarySensor(hass, - device, - sensor_type)) + if "stickup_cams" in SENSOR_TYPES[sensor_type][1]: + sensors.append(RingBinarySensor(hass, device, sensor_type)) add_entities(sensors, True) return True @@ -68,8 +73,9 @@ class RingBinarySensor(BinarySensorDevice): super(RingBinarySensor, self).__init__() self._sensor_type = sensor_type self._data = data - self._name = "{0} {1}".format(self._data.name, - SENSOR_TYPES.get(self._sensor_type)[0]) + self._name = "{0} {1}".format( + self._data.name, SENSOR_TYPES.get(self._sensor_type)[0] + ) self._device_class = SENSOR_TYPES.get(self._sensor_type)[2] self._state = None @@ -94,13 +100,13 @@ class RingBinarySensor(BinarySensorDevice): attrs = {} attrs[ATTR_ATTRIBUTION] = CONF_ATTRIBUTION - attrs['device_id'] = self._data.id - attrs['firmware'] = self._data.firmware - attrs['timezone'] = self._data.timezone + attrs["device_id"] = self._data.id + attrs["firmware"] = self._data.firmware + attrs["timezone"] = self._data.timezone if self._data.alert and self._data.alert_expires_at: - attrs['expires_at'] = self._data.alert_expires_at - attrs['state'] = self._data.alert.get('state') + attrs["expires_at"] = self._data.alert_expires_at + attrs["state"] = self._data.alert.get("state") return attrs @@ -109,8 +115,9 @@ class RingBinarySensor(BinarySensorDevice): self._data.check_alerts() if self._data.alert: - if self._sensor_type == self._data.alert.get('kind') and \ - self._data.account_id == self._data.alert.get('doorbot_id'): + if self._sensor_type == self._data.alert.get( + "kind" + ) and self._data.account_id == self._data.alert.get("doorbot_id"): self._state = True else: self._state = False diff --git a/homeassistant/components/binary_sensor/rpi_gpio.py b/homeassistant/components/binary_sensor/rpi_gpio.py index 2fe4e0766..a3bf28601 100644 --- a/homeassistant/components/binary_sensor/rpi_gpio.py +++ b/homeassistant/components/binary_sensor/rpi_gpio.py @@ -9,34 +9,33 @@ import logging import voluptuous as vol from homeassistant.components import rpi_gpio -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.const import DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_BOUNCETIME = 'bouncetime' -CONF_INVERT_LOGIC = 'invert_logic' -CONF_PORTS = 'ports' -CONF_PULL_MODE = 'pull_mode' +CONF_BOUNCETIME = "bouncetime" +CONF_INVERT_LOGIC = "invert_logic" +CONF_PORTS = "ports" +CONF_PULL_MODE = "pull_mode" DEFAULT_BOUNCETIME = 50 DEFAULT_INVERT_LOGIC = False -DEFAULT_PULL_MODE = 'UP' +DEFAULT_PULL_MODE = "UP" -DEPENDENCIES = ['rpi_gpio'] +DEPENDENCIES = ["rpi_gpio"] -_SENSORS_SCHEMA = vol.Schema({ - cv.positive_int: cv.string, -}) +_SENSORS_SCHEMA = vol.Schema({cv.positive_int: cv.string}) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PORTS): _SENSORS_SCHEMA, - vol.Optional(CONF_BOUNCETIME, default=DEFAULT_BOUNCETIME): cv.positive_int, - vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, - vol.Optional(CONF_PULL_MODE, default=DEFAULT_PULL_MODE): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_PORTS): _SENSORS_SCHEMA, + vol.Optional(CONF_BOUNCETIME, default=DEFAULT_BOUNCETIME): cv.positive_int, + vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, + vol.Optional(CONF_PULL_MODE, default=DEFAULT_PULL_MODE): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -46,10 +45,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): invert_logic = config.get(CONF_INVERT_LOGIC) binary_sensors = [] - ports = config.get('ports') + ports = config.get("ports") for port_num, port_name in ports.items(): - binary_sensors.append(RPiGPIOBinarySensor( - port_name, port_num, pull_mode, bouncetime, invert_logic)) + binary_sensors.append( + RPiGPIOBinarySensor( + port_name, port_num, pull_mode, bouncetime, invert_logic + ) + ) add_entities(binary_sensors, True) diff --git a/homeassistant/components/binary_sensor/rpi_pfio.py b/homeassistant/components/binary_sensor/rpi_pfio.py index 61d1f8ac2..9f820686c 100644 --- a/homeassistant/components/binary_sensor/rpi_pfio.py +++ b/homeassistant/components/binary_sensor/rpi_pfio.py @@ -8,35 +8,33 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import ( - PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.components import rpi_pfio from homeassistant.const import CONF_NAME, DEVICE_DEFAULT_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_INVERT_LOGIC = 'invert_logic' -CONF_PORTS = 'ports' -CONF_SETTLE_TIME = 'settle_time' +CONF_INVERT_LOGIC = "invert_logic" +CONF_PORTS = "ports" +CONF_SETTLE_TIME = "settle_time" DEFAULT_INVERT_LOGIC = False DEFAULT_SETTLE_TIME = 20 -DEPENDENCIES = ['rpi_pfio'] +DEPENDENCIES = ["rpi_pfio"] -PORT_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_SETTLE_TIME, default=DEFAULT_SETTLE_TIME): - cv.positive_int, - vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, -}) +PORT_SCHEMA = vol.Schema( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_SETTLE_TIME, default=DEFAULT_SETTLE_TIME): cv.positive_int, + vol.Optional(CONF_INVERT_LOGIC, default=DEFAULT_INVERT_LOGIC): cv.boolean, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_PORTS, default={}): vol.Schema({ - cv.positive_int: PORT_SCHEMA, - }) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_PORTS, default={}): vol.Schema({cv.positive_int: PORT_SCHEMA})} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -48,8 +46,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): settle_time = port_entity[CONF_SETTLE_TIME] / 1000 invert_logic = port_entity[CONF_INVERT_LOGIC] - binary_sensors.append(RPiPFIOBinarySensor( - hass, port, name, settle_time, invert_logic)) + binary_sensors.append( + RPiPFIOBinarySensor(hass, port, name, settle_time, invert_logic) + ) add_entities(binary_sensors, True) rpi_pfio.activate_listener(hass) diff --git a/homeassistant/components/binary_sensor/satel_integra.py b/homeassistant/components/binary_sensor/satel_integra.py index 3500f0a05..ac8c631ec 100644 --- a/homeassistant/components/binary_sensor/satel_integra.py +++ b/homeassistant/components/binary_sensor/satel_integra.py @@ -8,21 +8,22 @@ import asyncio import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.satel_integra import (CONF_ZONES, - CONF_ZONE_NAME, - CONF_ZONE_TYPE, - SIGNAL_ZONES_UPDATED) +from homeassistant.components.satel_integra import ( + CONF_ZONES, + CONF_ZONE_NAME, + CONF_ZONE_TYPE, + SIGNAL_ZONES_UPDATED, +) from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -DEPENDENCIES = ['satel_integra'] +DEPENDENCIES = ["satel_integra"] _LOGGER = logging.getLogger(__name__) @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 Satel Integra binary sensor devices.""" if not discovery_info: return @@ -53,8 +54,7 @@ class SatelIntegraBinarySensor(BinarySensorDevice): @asyncio.coroutine def async_added_to_hass(self): """Register callbacks.""" - async_dispatcher_connect( - self.hass, SIGNAL_ZONES_UPDATED, self._zones_updated) + async_dispatcher_connect(self.hass, SIGNAL_ZONES_UPDATED, self._zones_updated) @property def name(self): @@ -64,7 +64,7 @@ class SatelIntegraBinarySensor(BinarySensorDevice): @property def icon(self): """Icon for device by its type.""" - if self._zone_type == 'smoke': + if self._zone_type == "smoke": return "mdi:fire" @property @@ -85,7 +85,6 @@ class SatelIntegraBinarySensor(BinarySensorDevice): @callback def _zones_updated(self, zones): """Update the zone's state, if needed.""" - if self._zone_number in zones \ - and self._state != zones[self._zone_number]: + if self._zone_number in zones and self._state != zones[self._zone_number]: self._state = zones[self._zone_number] self.async_schedule_update_ha_state() diff --git a/homeassistant/components/binary_sensor/skybell.py b/homeassistant/components/binary_sensor/skybell.py index 7d8b3a84a..fd0b3d651 100644 --- a/homeassistant/components/binary_sensor/skybell.py +++ b/homeassistant/components/binary_sensor/skybell.py @@ -9,15 +9,16 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.components.skybell import ( - DEFAULT_ENTITY_NAMESPACE, DOMAIN as SKYBELL_DOMAIN, SkybellDevice) -from homeassistant.const import ( - CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS) + DEFAULT_ENTITY_NAMESPACE, + DOMAIN as SKYBELL_DOMAIN, + SkybellDevice, +) +from homeassistant.const import CONF_ENTITY_NAMESPACE, CONF_MONITORED_CONDITIONS import homeassistant.helpers.config_validation as cv -DEPENDENCIES = ['skybell'] +DEPENDENCIES = ["skybell"] _LOGGER = logging.getLogger(__name__) @@ -25,16 +26,20 @@ SCAN_INTERVAL = timedelta(seconds=5) # Sensor types: Name, device_class, event SENSOR_TYPES = { - 'button': ['Button', 'occupancy', 'device:sensor:button'], - 'motion': ['Motion', 'motion', 'device:sensor:motion'], + "button": ["Button", "occupancy", "device:sensor:button"], + "motion": ["Motion", "motion", "device:sensor:motion"], } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE): - cv.string, - vol.Required(CONF_MONITORED_CONDITIONS, default=[]): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional( + CONF_ENTITY_NAMESPACE, default=DEFAULT_ENTITY_NAMESPACE + ): cv.string, + vol.Required(CONF_MONITORED_CONDITIONS, default=[]): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -56,8 +61,9 @@ class SkybellBinarySensor(SkybellDevice, BinarySensorDevice): """Initialize a binary sensor for a Skybell device.""" super().__init__(device) self._sensor_type = sensor_type - self._name = "{0} {1}".format(self._device.name, - SENSOR_TYPES[self._sensor_type][0]) + self._name = "{0} {1}".format( + self._device.name, SENSOR_TYPES[self._sensor_type][0] + ) self._device_class = SENSOR_TYPES[self._sensor_type][1] self._event = {} self._state = None @@ -82,7 +88,7 @@ class SkybellBinarySensor(SkybellDevice, BinarySensorDevice): """Return the state attributes.""" attrs = super().device_state_attributes - attrs['event_date'] = self._event.get('createdAt') + attrs["event_date"] = self._event.get("createdAt") return attrs @@ -92,6 +98,6 @@ class SkybellBinarySensor(SkybellDevice, BinarySensorDevice): event = self._device.latest(SENSOR_TYPES[self._sensor_type][2]) - self._state = bool(event and event.get('id') != self._event.get('id')) + self._state = bool(event and event.get("id") != self._event.get("id")) self._event = event or {} diff --git a/homeassistant/components/binary_sensor/sleepiq.py b/homeassistant/components/binary_sensor/sleepiq.py index 808eda496..b0f920b52 100644 --- a/homeassistant/components/binary_sensor/sleepiq.py +++ b/homeassistant/components/binary_sensor/sleepiq.py @@ -7,7 +7,7 @@ https://home-assistant.io/components/binary_sensor.sleepiq/ from homeassistant.components import sleepiq from homeassistant.components.binary_sensor import BinarySensorDevice -DEPENDENCIES = ['sleepiq'] +DEPENDENCIES = ["sleepiq"] def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/binary_sensor/spc.py b/homeassistant/components/binary_sensor/spc.py index 9afd4fe40..5a1d18102 100644 --- a/homeassistant/components/binary_sensor/spc.py +++ b/homeassistant/components/binary_sensor/spc.py @@ -13,16 +13,9 @@ from homeassistant.const import STATE_OFF, STATE_ON, STATE_UNAVAILABLE _LOGGER = logging.getLogger(__name__) -SPC_TYPE_TO_DEVICE_CLASS = { - '0': 'motion', - '1': 'opening', - '3': 'smoke', -} +SPC_TYPE_TO_DEVICE_CLASS = {"0": "motion", "1": "opening", "3": "smoke"} -SPC_INPUT_TO_SENSOR_STATE = { - '0': STATE_OFF, - '1': STATE_ON, -} +SPC_INPUT_TO_SENSOR_STATE = {"0": STATE_OFF, "1": STATE_ON} def _get_device_class(spc_type): @@ -38,24 +31,25 @@ def _get_sensor_state(spc_input): def _create_sensor(hass, zone): """Create a SPC sensor.""" return SpcBinarySensor( - zone_id=zone['id'], name=zone['zone_name'], - state=_get_sensor_state(zone['input']), - device_class=_get_device_class(zone['type']), - spc_registry=hass.data[DATA_REGISTRY]) + zone_id=zone["id"], + name=zone["zone_name"], + state=_get_sensor_state(zone["input"]), + device_class=_get_device_class(zone["type"]), + spc_registry=hass.data[DATA_REGISTRY], + ) @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 SPC binary sensor.""" - if (discovery_info is None or - discovery_info[ATTR_DISCOVER_DEVICES] is None): + if discovery_info is None or discovery_info[ATTR_DISCOVER_DEVICES] is None: return async_add_entities( _create_sensor(hass, zone) for zone in discovery_info[ATTR_DISCOVER_DEVICES] - if _get_device_class(zone['type'])) + if _get_device_class(zone["type"]) + ) class SpcBinarySensor(BinarySensorDevice): diff --git a/homeassistant/components/binary_sensor/tahoma.py b/homeassistant/components/binary_sensor/tahoma.py index 7af5a730c..60e969f78 100644 --- a/homeassistant/components/binary_sensor/tahoma.py +++ b/homeassistant/components/binary_sensor/tahoma.py @@ -8,13 +8,11 @@ https://home-assistant.io/components/binary_sensor.tahoma/ import logging from datetime import timedelta -from homeassistant.components.binary_sensor import ( - BinarySensorDevice) -from homeassistant.components.tahoma import ( - DOMAIN as TAHOMA_DOMAIN, TahomaDevice) -from homeassistant.const import (STATE_OFF, STATE_ON, ATTR_BATTERY_LEVEL) +from homeassistant.components.binary_sensor import BinarySensorDevice +from homeassistant.components.tahoma import DOMAIN as TAHOMA_DOMAIN, TahomaDevice +from homeassistant.const import STATE_OFF, STATE_ON, ATTR_BATTERY_LEVEL -DEPENDENCIES = ['tahoma'] +DEPENDENCIES = ["tahoma"] _LOGGER = logging.getLogger(__name__) @@ -24,9 +22,9 @@ SCAN_INTERVAL = timedelta(seconds=120) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up Tahoma controller devices.""" _LOGGER.debug("Setup Tahoma Binary sensor platform") - controller = hass.data[TAHOMA_DOMAIN]['controller'] + controller = hass.data[TAHOMA_DOMAIN]["controller"] devices = [] - for device in hass.data[TAHOMA_DOMAIN]['devices']['smoke']: + for device in hass.data[TAHOMA_DOMAIN]["devices"]["smoke"]: devices.append(TahomaBinarySensor(device, controller)) add_entities(devices, True) @@ -50,8 +48,8 @@ class TahomaBinarySensor(TahomaDevice, BinarySensorDevice): @property def device_class(self): """Return the class of the device.""" - if self.tahoma_device.type == 'rtds:RTDSSmokeSensor': - return 'smoke' + if self.tahoma_device.type == "rtds:RTDSSmokeSensor": + return "smoke" return None @property @@ -74,23 +72,21 @@ class TahomaBinarySensor(TahomaDevice, BinarySensorDevice): def update(self): """Update the state.""" self.controller.get_states([self.tahoma_device]) - if self.tahoma_device.type == 'rtds:RTDSSmokeSensor': - if self.tahoma_device.active_states['core:SmokeState']\ - == 'notDetected': + if self.tahoma_device.type == "rtds:RTDSSmokeSensor": + if self.tahoma_device.active_states["core:SmokeState"] == "notDetected": self._state = STATE_OFF else: self._state = STATE_ON - if 'core:SensorDefectState' in self.tahoma_device.active_states: + if "core:SensorDefectState" in self.tahoma_device.active_states: # Set to 'lowBattery' for low battery warning. - self._battery = self.tahoma_device.active_states[ - 'core:SensorDefectState'] + self._battery = self.tahoma_device.active_states["core:SensorDefectState"] else: self._battery = None if self._state == STATE_ON: self._icon = "mdi:fire" - elif self._battery == 'lowBattery': + elif self._battery == "lowBattery": self._icon = "mdi:battery-alert" else: self._icon = None diff --git a/homeassistant/components/binary_sensor/tapsaff.py b/homeassistant/components/binary_sensor/tapsaff.py index 1978a127c..e99886b0e 100644 --- a/homeassistant/components/binary_sensor/tapsaff.py +++ b/homeassistant/components/binary_sensor/tapsaff.py @@ -9,25 +9,26 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import ( - PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import CONF_NAME import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['tapsaff==0.2.0'] +REQUIREMENTS = ["tapsaff==0.2.0"] _LOGGER = logging.getLogger(__name__) -CONF_LOCATION = 'location' +CONF_LOCATION = "location" -DEFAULT_NAME = 'Taps Aff' +DEFAULT_NAME = "Taps Aff" SCAN_INTERVAL = timedelta(minutes=30) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_LOCATION): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_LOCATION): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -51,7 +52,7 @@ class TapsAffSensor(BinarySensorDevice): @property def name(self): """Return the name of the sensor.""" - return '{}'.format(self._name) + return "{}".format(self._name) @property def is_on(self): diff --git a/homeassistant/components/binary_sensor/tcp.py b/homeassistant/components/binary_sensor/tcp.py index 764b6829c..4b8937829 100644 --- a/homeassistant/components/binary_sensor/tcp.py +++ b/homeassistant/components/binary_sensor/tcp.py @@ -8,7 +8,10 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.sensor.tcp import ( - TcpSensor, CONF_VALUE_ON, PLATFORM_SCHEMA) + TcpSensor, + CONF_VALUE_ON, + PLATFORM_SCHEMA, +) _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/binary_sensor/tellduslive.py b/homeassistant/components/binary_sensor/tellduslive.py index 450a5e580..78a0fd499 100644 --- a/homeassistant/components/binary_sensor/tellduslive.py +++ b/homeassistant/components/binary_sensor/tellduslive.py @@ -20,8 +20,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if discovery_info is None: return add_entities( - TelldusLiveSensor(hass, binary_sensor) - for binary_sensor in discovery_info + TelldusLiveSensor(hass, binary_sensor) for binary_sensor in discovery_info ) diff --git a/homeassistant/components/binary_sensor/template.py b/homeassistant/components/binary_sensor/template.py index c5bfa5930..756e646f5 100644 --- a/homeassistant/components/binary_sensor/template.py +++ b/homeassistant/components/binary_sensor/template.py @@ -11,54 +11,61 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.components.binary_sensor import ( - BinarySensorDevice, ENTITY_ID_FORMAT, PLATFORM_SCHEMA, - DEVICE_CLASSES_SCHEMA) + BinarySensorDevice, + ENTITY_ID_FORMAT, + PLATFORM_SCHEMA, + DEVICE_CLASSES_SCHEMA, +) from homeassistant.const import ( - ATTR_FRIENDLY_NAME, ATTR_ENTITY_ID, CONF_VALUE_TEMPLATE, - CONF_ICON_TEMPLATE, CONF_ENTITY_PICTURE_TEMPLATE, - CONF_SENSORS, CONF_DEVICE_CLASS, EVENT_HOMEASSISTANT_START) + ATTR_FRIENDLY_NAME, + ATTR_ENTITY_ID, + CONF_VALUE_TEMPLATE, + CONF_ICON_TEMPLATE, + CONF_ENTITY_PICTURE_TEMPLATE, + CONF_SENSORS, + CONF_DEVICE_CLASS, + EVENT_HOMEASSISTANT_START, +) from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id -from homeassistant.helpers.event import ( - async_track_state_change, async_track_same_state) +from homeassistant.helpers.event import async_track_state_change, async_track_same_state _LOGGER = logging.getLogger(__name__) -CONF_DELAY_ON = 'delay_on' -CONF_DELAY_OFF = 'delay_off' +CONF_DELAY_ON = "delay_on" +CONF_DELAY_OFF = "delay_off" -SENSOR_SCHEMA = vol.Schema({ - vol.Required(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_ICON_TEMPLATE): cv.template, - vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template, - vol.Optional(ATTR_FRIENDLY_NAME): cv.string, - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_DELAY_ON): - vol.All(cv.time_period, cv.positive_timedelta), - vol.Optional(CONF_DELAY_OFF): - vol.All(cv.time_period, cv.positive_timedelta), -}) +SENSOR_SCHEMA = vol.Schema( + { + vol.Required(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_ICON_TEMPLATE): cv.template, + vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template, + vol.Optional(ATTR_FRIENDLY_NAME): cv.string, + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_DELAY_ON): vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_DELAY_OFF): vol.All(cv.time_period, cv.positive_timedelta), + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA})} +) @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 template binary sensors.""" sensors = [] for device, device_config in config[CONF_SENSORS].items(): value_template = device_config[CONF_VALUE_TEMPLATE] icon_template = device_config.get(CONF_ICON_TEMPLATE) - entity_picture_template = device_config.get( - CONF_ENTITY_PICTURE_TEMPLATE) - entity_ids = (device_config.get(ATTR_ENTITY_ID) or - value_template.extract_entities()) + entity_picture_template = device_config.get(CONF_ENTITY_PICTURE_TEMPLATE) + entity_ids = ( + device_config.get(ATTR_ENTITY_ID) or value_template.extract_entities() + ) friendly_name = device_config.get(ATTR_FRIENDLY_NAME, device) device_class = device_config.get(CONF_DEVICE_CLASS) delay_on = device_config.get(CONF_DELAY_ON) @@ -75,10 +82,18 @@ def async_setup_platform(hass, config, async_add_entities, sensors.append( BinarySensorTemplate( - hass, device, friendly_name, device_class, value_template, - icon_template, entity_picture_template, entity_ids, - delay_on, delay_off) + hass, + device, + friendly_name, + device_class, + value_template, + icon_template, + entity_picture_template, + entity_ids, + delay_on, + delay_off, ) + ) if not sensors: _LOGGER.error("No sensors added") return False @@ -90,13 +105,22 @@ def async_setup_platform(hass, config, async_add_entities, class BinarySensorTemplate(BinarySensorDevice): """A virtual binary sensor that triggers from another sensor.""" - def __init__(self, hass, device, friendly_name, device_class, - value_template, icon_template, entity_picture_template, - entity_ids, delay_on, delay_off): + def __init__( + self, + hass, + device, + friendly_name, + device_class, + value_template, + icon_template, + entity_picture_template, + entity_ids, + delay_on, + delay_off, + ): """Initialize the Template binary sensor.""" self.hass = hass - self.entity_id = async_generate_entity_id( - ENTITY_ID_FORMAT, device, hass=hass) + self.entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, device, hass=hass) self._name = friendly_name self._device_class = device_class self._template = value_template @@ -112,6 +136,7 @@ class BinarySensorTemplate(BinarySensorDevice): @asyncio.coroutine def async_added_to_hass(self): """Register callbacks.""" + @callback def template_bsensor_state_listener(entity, old_state, new_state): """Handle the target device state changes.""" @@ -121,12 +146,14 @@ class BinarySensorTemplate(BinarySensorDevice): def template_bsensor_startup(event): """Update template on startup.""" async_track_state_change( - self.hass, self._entities, template_bsensor_state_listener) + self.hass, self._entities, template_bsensor_state_listener + ) self.hass.async_add_job(self.async_check_state) self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, template_bsensor_startup) + EVENT_HOMEASSISTANT_START, template_bsensor_startup + ) @property def name(self): @@ -163,35 +190,45 @@ class BinarySensorTemplate(BinarySensorDevice): """Get the state of template.""" state = None try: - state = (self._template.async_render().lower() == 'true') + state = self._template.async_render().lower() == "true" except TemplateError as ex: if ex.args and ex.args[0].startswith( - "UndefinedError: 'None' has no attribute"): + "UndefinedError: 'None' has no attribute" + ): # Common during HA startup - so just a warning - _LOGGER.warning("Could not render template %s, " - "the state is unknown", self._name) + _LOGGER.warning( + "Could not render template %s, " "the state is unknown", self._name + ) return _LOGGER.error("Could not render template %s: %s", self._name, ex) for property_name, template in ( - ('_icon', self._icon_template), - ('_entity_picture', self._entity_picture_template)): + ("_icon", self._icon_template), + ("_entity_picture", self._entity_picture_template), + ): if template is None: continue try: setattr(self, property_name, template.async_render()) except TemplateError as ex: - friendly_property_name = property_name[1:].replace('_', ' ') + friendly_property_name = property_name[1:].replace("_", " ") if ex.args and ex.args[0].startswith( - "UndefinedError: 'None' has no attribute"): + "UndefinedError: 'None' has no attribute" + ): # Common during HA startup - so just a warning - _LOGGER.warning('Could not render %s template %s,' - ' the state is unknown.', - friendly_property_name, self._name) + _LOGGER.warning( + "Could not render %s template %s," " the state is unknown.", + friendly_property_name, + self._name, + ) else: - _LOGGER.error('Could not render %s template %s: %s', - friendly_property_name, self._name, ex) + _LOGGER.error( + "Could not render %s template %s: %s", + friendly_property_name, + self._name, + ex, + ) return state return state @@ -212,12 +249,15 @@ class BinarySensorTemplate(BinarySensorDevice): self.async_schedule_update_ha_state() # state without delay - if (state and not self._delay_on) or \ - (not state and not self._delay_off): + if (state and not self._delay_on) or (not state and not self._delay_off): set_state() return period = self._delay_on if state else self._delay_off async_track_same_state( - self.hass, period, set_state, entity_ids=self._entities, - async_check_same_func=lambda *args: self._async_render() == state) + self.hass, + period, + set_state, + entity_ids=self._entities, + async_check_same_func=lambda *args: self._async_render() == state, + ) diff --git a/homeassistant/components/binary_sensor/tesla.py b/homeassistant/components/binary_sensor/tesla.py index f7613d74d..8eea1a4ab 100644 --- a/homeassistant/components/binary_sensor/tesla.py +++ b/homeassistant/components/binary_sensor/tesla.py @@ -6,21 +6,20 @@ https://home-assistant.io/components/binary_sensor.tesla/ """ import logging -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, ENTITY_ID_FORMAT) +from homeassistant.components.binary_sensor import BinarySensorDevice, ENTITY_ID_FORMAT from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN, TeslaDevice _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['tesla'] +DEPENDENCIES = ["tesla"] def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Tesla binary sensor.""" devices = [ - TeslaBinarySensor( - device, hass.data[TESLA_DOMAIN]['controller'], 'connectivity') - for device in hass.data[TESLA_DOMAIN]['devices']['binary_sensor']] + TeslaBinarySensor(device, hass.data[TESLA_DOMAIN]["controller"], "connectivity") + for device in hass.data[TESLA_DOMAIN]["devices"]["binary_sensor"] + ] add_entities(devices, True) diff --git a/homeassistant/components/binary_sensor/threshold.py b/homeassistant/components/binary_sensor/threshold.py index fd7ead088..16f38f4dd 100644 --- a/homeassistant/components/binary_sensor/threshold.py +++ b/homeassistant/components/binary_sensor/threshold.py @@ -10,53 +10,60 @@ import logging import voluptuous as vol from homeassistant.components.binary_sensor import ( - DEVICE_CLASSES_SCHEMA, PLATFORM_SCHEMA, BinarySensorDevice) + DEVICE_CLASSES_SCHEMA, + PLATFORM_SCHEMA, + BinarySensorDevice, +) from homeassistant.const import ( - ATTR_ENTITY_ID, CONF_DEVICE_CLASS, CONF_ENTITY_ID, CONF_NAME, - STATE_UNKNOWN) + ATTR_ENTITY_ID, + CONF_DEVICE_CLASS, + CONF_ENTITY_ID, + CONF_NAME, + STATE_UNKNOWN, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change _LOGGER = logging.getLogger(__name__) -ATTR_HYSTERESIS = 'hysteresis' -ATTR_LOWER = 'lower' -ATTR_POSITION = 'position' -ATTR_SENSOR_VALUE = 'sensor_value' -ATTR_TYPE = 'type' -ATTR_UPPER = 'upper' +ATTR_HYSTERESIS = "hysteresis" +ATTR_LOWER = "lower" +ATTR_POSITION = "position" +ATTR_SENSOR_VALUE = "sensor_value" +ATTR_TYPE = "type" +ATTR_UPPER = "upper" -CONF_HYSTERESIS = 'hysteresis' -CONF_LOWER = 'lower' -CONF_UPPER = 'upper' +CONF_HYSTERESIS = "hysteresis" +CONF_LOWER = "lower" +CONF_UPPER = "upper" -DEFAULT_NAME = 'Threshold' +DEFAULT_NAME = "Threshold" DEFAULT_HYSTERESIS = 0.0 -POSITION_ABOVE = 'above' -POSITION_BELOW = 'below' -POSITION_IN_RANGE = 'in_range' -POSITION_UNKNOWN = 'unknown' +POSITION_ABOVE = "above" +POSITION_BELOW = "below" +POSITION_IN_RANGE = "in_range" +POSITION_UNKNOWN = "unknown" -TYPE_LOWER = 'lower' -TYPE_RANGE = 'range' -TYPE_UPPER = 'upper' +TYPE_LOWER = "lower" +TYPE_RANGE = "range" +TYPE_UPPER = "upper" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_HYSTERESIS, default=DEFAULT_HYSTERESIS): - vol.Coerce(float), - vol.Optional(CONF_LOWER): vol.Coerce(float), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_UPPER): vol.Coerce(float), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_HYSTERESIS, default=DEFAULT_HYSTERESIS): vol.Coerce(float), + vol.Optional(CONF_LOWER): vol.Coerce(float), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_UPPER): vol.Coerce(float), + } +) @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 Threshold sensor.""" entity_id = config.get(CONF_ENTITY_ID) name = config.get(CONF_NAME) @@ -65,15 +72,20 @@ def async_setup_platform(hass, config, async_add_entities, hysteresis = config.get(CONF_HYSTERESIS) device_class = config.get(CONF_DEVICE_CLASS) - async_add_entities([ThresholdSensor( - hass, entity_id, name, lower, upper, hysteresis, device_class)], True) + async_add_entities( + [ + ThresholdSensor( + hass, entity_id, name, lower, upper, hysteresis, device_class + ) + ], + True, + ) class ThresholdSensor(BinarySensorDevice): """Representation of a Threshold sensor.""" - def __init__(self, hass, entity_id, name, lower, upper, hysteresis, - device_class): + def __init__(self, hass, entity_id, name, lower, upper, hysteresis, device_class): """Initialize the Threshold sensor.""" self._hass = hass self._entity_id = entity_id @@ -88,20 +100,19 @@ class ThresholdSensor(BinarySensorDevice): self.sensor_value = None @callback - def async_threshold_sensor_state_listener( - entity, old_state, new_state): + def async_threshold_sensor_state_listener(entity, old_state, new_state): """Handle sensor state changes.""" try: - self.sensor_value = None if new_state.state == STATE_UNKNOWN \ - else float(new_state.state) + self.sensor_value = ( + None if new_state.state == STATE_UNKNOWN else float(new_state.state) + ) except (ValueError, TypeError): self.sensor_value = None _LOGGER.warning("State is not numerical") hass.async_add_job(self.async_update_ha_state, True) - async_track_state_change( - hass, entity_id, async_threshold_sensor_state_listener) + async_track_state_change(hass, entity_id, async_threshold_sensor_state_listener) @property def name(self): @@ -126,8 +137,7 @@ class ThresholdSensor(BinarySensorDevice): @property def threshold_type(self): """Return the type of threshold this sensor represents.""" - if self._threshold_lower is not None and \ - self._threshold_upper is not None: + if self._threshold_lower is not None and self._threshold_upper is not None: return TYPE_RANGE if self._threshold_lower is not None: return TYPE_LOWER @@ -150,6 +160,7 @@ class ThresholdSensor(BinarySensorDevice): @asyncio.coroutine def async_update(self): """Get the latest data and updates the states.""" + def below(threshold): """Determine if the sensor value is below a threshold.""" return self.sensor_value < (threshold - self._hysteresis) diff --git a/homeassistant/components/binary_sensor/trend.py b/homeassistant/components/binary_sensor/trend.py index ae6fd5562..a91d8fd86 100644 --- a/homeassistant/components/binary_sensor/trend.py +++ b/homeassistant/components/binary_sensor/trend.py @@ -12,49 +12,59 @@ import math import voluptuous as vol from homeassistant.components.binary_sensor import ( - DEVICE_CLASSES_SCHEMA, ENTITY_ID_FORMAT, PLATFORM_SCHEMA, - BinarySensorDevice) + DEVICE_CLASSES_SCHEMA, + ENTITY_ID_FORMAT, + PLATFORM_SCHEMA, + BinarySensorDevice, +) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_FRIENDLY_NAME, CONF_DEVICE_CLASS, CONF_ENTITY_ID, - CONF_FRIENDLY_NAME, STATE_UNKNOWN) + ATTR_ENTITY_ID, + ATTR_FRIENDLY_NAME, + CONF_DEVICE_CLASS, + CONF_ENTITY_ID, + CONF_FRIENDLY_NAME, + STATE_UNKNOWN, +) from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import generate_entity_id from homeassistant.helpers.event import async_track_state_change from homeassistant.util import utcnow -REQUIREMENTS = ['numpy==1.15.1'] +REQUIREMENTS = ["numpy==1.15.1"] _LOGGER = logging.getLogger(__name__) -ATTR_ATTRIBUTE = 'attribute' -ATTR_GRADIENT = 'gradient' -ATTR_MIN_GRADIENT = 'min_gradient' -ATTR_INVERT = 'invert' -ATTR_SAMPLE_DURATION = 'sample_duration' -ATTR_SAMPLE_COUNT = 'sample_count' +ATTR_ATTRIBUTE = "attribute" +ATTR_GRADIENT = "gradient" +ATTR_MIN_GRADIENT = "min_gradient" +ATTR_INVERT = "invert" +ATTR_SAMPLE_DURATION = "sample_duration" +ATTR_SAMPLE_COUNT = "sample_count" -CONF_ATTRIBUTE = 'attribute' -CONF_INVERT = 'invert' -CONF_MAX_SAMPLES = 'max_samples' -CONF_MIN_GRADIENT = 'min_gradient' -CONF_SAMPLE_DURATION = 'sample_duration' -CONF_SENSORS = 'sensors' +CONF_ATTRIBUTE = "attribute" +CONF_INVERT = "invert" +CONF_MAX_SAMPLES = "max_samples" +CONF_MIN_GRADIENT = "min_gradient" +CONF_SAMPLE_DURATION = "sample_duration" +CONF_SENSORS = "sensors" -SENSOR_SCHEMA = vol.Schema({ - vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Optional(CONF_ATTRIBUTE): cv.string, - vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, - vol.Optional(CONF_FRIENDLY_NAME): cv.string, - vol.Optional(CONF_INVERT, default=False): cv.boolean, - vol.Optional(CONF_MAX_SAMPLES, default=2): cv.positive_int, - vol.Optional(CONF_MIN_GRADIENT, default=0.0): vol.Coerce(float), - vol.Optional(CONF_SAMPLE_DURATION, default=0): cv.positive_int, -}) +SENSOR_SCHEMA = vol.Schema( + { + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Optional(CONF_ATTRIBUTE): cv.string, + vol.Optional(CONF_DEVICE_CLASS): DEVICE_CLASSES_SCHEMA, + vol.Optional(CONF_FRIENDLY_NAME): cv.string, + vol.Optional(CONF_INVERT, default=False): cv.boolean, + vol.Optional(CONF_MAX_SAMPLES, default=2): cv.positive_int, + vol.Optional(CONF_MIN_GRADIENT, default=0.0): vol.Coerce(float), + vol.Optional(CONF_SAMPLE_DURATION, default=0): cv.positive_int, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_SENSORS): vol.Schema({cv.slug: SENSOR_SCHEMA})} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -73,10 +83,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): sensors.append( SensorTrend( - hass, device_id, friendly_name, entity_id, attribute, - device_class, invert, max_samples, min_gradient, - sample_duration) + hass, + device_id, + friendly_name, + entity_id, + attribute, + device_class, + invert, + max_samples, + min_gradient, + sample_duration, ) + ) if not sensors: _LOGGER.error("No sensors added") return False @@ -87,13 +105,22 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class SensorTrend(BinarySensorDevice): """Representation of a trend Sensor.""" - def __init__(self, hass, device_id, friendly_name, entity_id, - attribute, device_class, invert, max_samples, - min_gradient, sample_duration): + def __init__( + self, + hass, + device_id, + friendly_name, + entity_id, + attribute, + device_class, + invert, + max_samples, + min_gradient, + sample_duration, + ): """Initialize the sensor.""" self._hass = hass - self.entity_id = generate_entity_id( - ENTITY_ID_FORMAT, device_id, hass=hass) + self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, device_id, hass=hass) self._name = friendly_name self._entity_id = entity_id self._attribute = attribute @@ -141,6 +168,7 @@ class SensorTrend(BinarySensorDevice): @asyncio.coroutine def async_added_to_hass(self): """Complete device setup after being added to hass.""" + @callback def trend_sensor_state_listener(entity, old_state, new_state): """Handle state changes on the observed device.""" @@ -157,8 +185,8 @@ class SensorTrend(BinarySensorDevice): _LOGGER.error(ex) async_track_state_change( - self.hass, self._entity_id, - trend_sensor_state_listener) + self.hass, self._entity_id, trend_sensor_state_listener + ) @asyncio.coroutine def async_update(self): @@ -177,8 +205,8 @@ class SensorTrend(BinarySensorDevice): # Update state self._state = ( - abs(self._gradient) > abs(self._min_gradient) and - math.copysign(self._gradient, self._min_gradient) == self._gradient + abs(self._gradient) > abs(self._min_gradient) + and math.copysign(self._gradient, self._min_gradient) == self._gradient ) if self._invert: @@ -190,6 +218,7 @@ class SensorTrend(BinarySensorDevice): This need run inside executor. """ import numpy as np + timestamps = np.array([t for t, _ in self.samples]) values = np.array([s for _, s in self.samples]) coeffs = np.polyfit(timestamps, values, 1) diff --git a/homeassistant/components/binary_sensor/upcloud.py b/homeassistant/components/binary_sensor/upcloud.py index c7b8a284d..9a06073c3 100644 --- a/homeassistant/components/binary_sensor/upcloud.py +++ b/homeassistant/components/binary_sensor/upcloud.py @@ -9,18 +9,20 @@ import logging import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.components.upcloud import ( - UpCloudServerEntity, CONF_SERVERS, DATA_UPCLOUD) + UpCloudServerEntity, + CONF_SERVERS, + DATA_UPCLOUD, +) _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['upcloud'] +DEPENDENCIES = ["upcloud"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SERVERS): vol.All(cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_SERVERS): vol.All(cv.ensure_list, [cv.string])} +) def setup_platform(hass, config, add_entities, discovery_info=None): diff --git a/homeassistant/components/binary_sensor/uptimerobot.py b/homeassistant/components/binary_sensor/uptimerobot.py index dbb83e53e..16e4c0252 100644 --- a/homeassistant/components/binary_sensor/uptimerobot.py +++ b/homeassistant/components/binary_sensor/uptimerobot.py @@ -8,22 +8,19 @@ import logging import voluptuous as vol -from homeassistant.components.binary_sensor import ( - PLATFORM_SCHEMA, BinarySensorDevice) +from homeassistant.components.binary_sensor import PLATFORM_SCHEMA, BinarySensorDevice from homeassistant.const import ATTR_ATTRIBUTION, CONF_API_KEY import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyuptimerobot==0.0.5'] +REQUIREMENTS = ["pyuptimerobot==0.0.5"] _LOGGER = logging.getLogger(__name__) -ATTR_TARGET = 'target' +ATTR_TARGET = "target" CONF_ATTRIBUTION = "Data provided by Uptime Robot" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_API_KEY): cv.string}) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -35,14 +32,20 @@ def setup_platform(hass, config, add_entities, discovery_info=None): monitors = up_robot.getMonitors(api_key) devices = [] - if not monitors or monitors.get('stat') != 'ok': + if not monitors or monitors.get("stat") != "ok": _LOGGER.error("Error connecting to Uptime Robot") return - for monitor in monitors['monitors']: - devices.append(UptimeRobotBinarySensor( - api_key, up_robot, monitor['id'], monitor['friendly_name'], - monitor['url'])) + for monitor in monitors["monitors"]: + devices.append( + UptimeRobotBinarySensor( + api_key, + up_robot, + monitor["id"], + monitor["friendly_name"], + monitor["url"], + ) + ) add_entities(devices, True) @@ -72,21 +75,18 @@ class UptimeRobotBinarySensor(BinarySensorDevice): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return 'connectivity' + return "connectivity" @property def device_state_attributes(self): """Return the state attributes of the binary sensor.""" - return { - ATTR_ATTRIBUTION: CONF_ATTRIBUTION, - ATTR_TARGET: self._target, - } + return {ATTR_ATTRIBUTION: CONF_ATTRIBUTION, ATTR_TARGET: self._target} def update(self): """Get the latest state of the binary sensor.""" monitor = self._up_robot.getMonitors(self._api_key, self._monitor_id) - if not monitor or monitor.get('stat') != 'ok': + if not monitor or monitor.get("stat") != "ok": _LOGGER.warning("Failed to get new state") return - status = monitor['monitors'][0]['status'] + status = monitor["monitors"][0]["status"] self._state = 1 if status == 2 else 0 diff --git a/homeassistant/components/binary_sensor/velbus.py b/homeassistant/components/binary_sensor/velbus.py index b123b9585..20d583078 100644 --- a/homeassistant/components/binary_sensor/velbus.py +++ b/homeassistant/components/binary_sensor/velbus.py @@ -7,16 +7,14 @@ https://home-assistant.io/components/binary_sensor.velbus/ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.velbus import ( - DOMAIN as VELBUS_DOMAIN, VelbusEntity) +from homeassistant.components.velbus import DOMAIN as VELBUS_DOMAIN, VelbusEntity _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['velbus'] +DEPENDENCIES = ["velbus"] -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 Velbus binary sensors.""" if discovery_info is None: return diff --git a/homeassistant/components/binary_sensor/vera.py b/homeassistant/components/binary_sensor/vera.py index bb1e7331d..d6150f07e 100644 --- a/homeassistant/components/binary_sensor/vera.py +++ b/homeassistant/components/binary_sensor/vera.py @@ -6,12 +6,10 @@ https://home-assistant.io/components/binary_sensor.vera/ """ import logging -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, ENTITY_ID_FORMAT) -from homeassistant.components.vera import ( - VERA_CONTROLLER, VERA_DEVICES, VeraDevice) +from homeassistant.components.binary_sensor import BinarySensorDevice, ENTITY_ID_FORMAT +from homeassistant.components.vera import VERA_CONTROLLER, VERA_DEVICES, VeraDevice -DEPENDENCIES = ['vera'] +DEPENDENCIES = ["vera"] _LOGGER = logging.getLogger(__name__) @@ -19,8 +17,12 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): """Perform the setup for Vera controller devices.""" add_entities( - [VeraBinarySensor(device, hass.data[VERA_CONTROLLER]) - for device in hass.data[VERA_DEVICES]['binary_sensor']], True) + [ + VeraBinarySensor(device, hass.data[VERA_CONTROLLER]) + for device in hass.data[VERA_DEVICES]["binary_sensor"] + ], + True, + ) class VeraBinarySensor(VeraDevice, BinarySensorDevice): diff --git a/homeassistant/components/binary_sensor/verisure.py b/homeassistant/components/binary_sensor/verisure.py index e040da959..d6301df65 100644 --- a/homeassistant/components/binary_sensor/verisure.py +++ b/homeassistant/components/binary_sensor/verisure.py @@ -19,10 +19,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hub.update_overview() if int(hub.config.get(CONF_DOOR_WINDOW, 1)): - sensors.extend([ - VerisureDoorWindowSensor(device_label) - for device_label in hub.get( - "$.doorWindow.doorWindowDevice[*].deviceLabel")]) + sensors.extend( + [ + VerisureDoorWindowSensor(device_label) + for device_label in hub.get( + "$.doorWindow.doorWindowDevice[*].deviceLabel" + ) + ] + ) add_entities(sensors) @@ -38,21 +42,30 @@ class VerisureDoorWindowSensor(BinarySensorDevice): """Return the name of the binary sensor.""" return hub.get_first( "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].area", - self._device_label) + self._device_label, + ) @property def is_on(self): """Return the state of the sensor.""" - return hub.get_first( - "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].state", - self._device_label) == "OPEN" + return ( + hub.get_first( + "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')].state", + self._device_label, + ) + == "OPEN" + ) @property def available(self): """Return True if entity is available.""" - return hub.get_first( - "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]", - self._device_label) is not None + return ( + hub.get_first( + "$.doorWindow.doorWindowDevice[?(@.deviceLabel=='%s')]", + self._device_label, + ) + is not None + ) # pylint: disable=no-self-use def update(self): diff --git a/homeassistant/components/binary_sensor/volvooncall.py b/homeassistant/components/binary_sensor/volvooncall.py index e70d30988..a2b911321 100644 --- a/homeassistant/components/binary_sensor/volvooncall.py +++ b/homeassistant/components/binary_sensor/volvooncall.py @@ -26,13 +26,13 @@ class VolvoSensor(VolvoEntity, BinarySensorDevice): def is_on(self): """Return True if the binary sensor is on.""" val = getattr(self.vehicle, self._attribute) - if self._attribute == 'bulb_failures': + if self._attribute == "bulb_failures": return bool(val) - if self._attribute in ['doors', 'windows']: - return any([val[key] for key in val if 'Open' in key]) - return val != 'Normal' + if self._attribute in ["doors", "windows"]: + return any([val[key] for key in val if "Open" in key]) + return val != "Normal" @property def device_class(self): """Return the class of this sensor, from DEVICE_CLASSES.""" - return 'safety' + return "safety" diff --git a/homeassistant/components/binary_sensor/vultr.py b/homeassistant/components/binary_sensor/vultr.py index 149a6c282..f604e7391 100644 --- a/homeassistant/components/binary_sensor/vultr.py +++ b/homeassistant/components/binary_sensor/vultr.py @@ -10,24 +10,37 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_NAME -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.components.vultr import ( - CONF_SUBSCRIPTION, ATTR_AUTO_BACKUPS, ATTR_ALLOWED_BANDWIDTH, - ATTR_CREATED_AT, ATTR_SUBSCRIPTION_ID, ATTR_SUBSCRIPTION_NAME, - ATTR_IPV4_ADDRESS, ATTR_IPV6_ADDRESS, ATTR_MEMORY, ATTR_DISK, - ATTR_COST_PER_MONTH, ATTR_OS, ATTR_REGION, ATTR_VCPUS, DATA_VULTR) + CONF_SUBSCRIPTION, + ATTR_AUTO_BACKUPS, + ATTR_ALLOWED_BANDWIDTH, + ATTR_CREATED_AT, + ATTR_SUBSCRIPTION_ID, + ATTR_SUBSCRIPTION_NAME, + ATTR_IPV4_ADDRESS, + ATTR_IPV6_ADDRESS, + ATTR_MEMORY, + ATTR_DISK, + ATTR_COST_PER_MONTH, + ATTR_OS, + ATTR_REGION, + ATTR_VCPUS, + DATA_VULTR, +) _LOGGER = logging.getLogger(__name__) -DEFAULT_DEVICE_CLASS = 'power' -DEFAULT_NAME = 'Vultr {}' -DEPENDENCIES = ['vultr'] +DEFAULT_DEVICE_CLASS = "power" +DEFAULT_NAME = "Vultr {}" +DEPENDENCIES = ["vultr"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SUBSCRIPTION): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_SUBSCRIPTION): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -59,19 +72,19 @@ class VultrBinarySensor(BinarySensorDevice): def name(self): """Return the name of the sensor.""" try: - return self._name.format(self.data['label']) + return self._name.format(self.data["label"]) except (KeyError, TypeError): return self._name @property def icon(self): """Return the icon of this server.""" - return 'mdi:server' if self.is_on else 'mdi:server-off' + return "mdi:server" if self.is_on else "mdi:server-off" @property def is_on(self): """Return true if the binary sensor is on.""" - return self.data['power_status'] == 'running' + return self.data["power_status"] == "running" @property def device_class(self): @@ -82,19 +95,19 @@ class VultrBinarySensor(BinarySensorDevice): def device_state_attributes(self): """Return the state attributes of the Vultr subscription.""" return { - ATTR_ALLOWED_BANDWIDTH: self.data.get('allowed_bandwidth_gb'), - ATTR_AUTO_BACKUPS: self.data.get('auto_backups'), - ATTR_COST_PER_MONTH: self.data.get('cost_per_month'), - ATTR_CREATED_AT: self.data.get('date_created'), - ATTR_DISK: self.data.get('disk'), - ATTR_IPV4_ADDRESS: self.data.get('main_ip'), - ATTR_IPV6_ADDRESS: self.data.get('v6_main_ip'), - ATTR_MEMORY: self.data.get('ram'), - ATTR_OS: self.data.get('os'), - ATTR_REGION: self.data.get('location'), - ATTR_SUBSCRIPTION_ID: self.data.get('SUBID'), - ATTR_SUBSCRIPTION_NAME: self.data.get('label'), - ATTR_VCPUS: self.data.get('vcpu_count') + ATTR_ALLOWED_BANDWIDTH: self.data.get("allowed_bandwidth_gb"), + ATTR_AUTO_BACKUPS: self.data.get("auto_backups"), + ATTR_COST_PER_MONTH: self.data.get("cost_per_month"), + ATTR_CREATED_AT: self.data.get("date_created"), + ATTR_DISK: self.data.get("disk"), + ATTR_IPV4_ADDRESS: self.data.get("main_ip"), + ATTR_IPV6_ADDRESS: self.data.get("v6_main_ip"), + ATTR_MEMORY: self.data.get("ram"), + ATTR_OS: self.data.get("os"), + ATTR_REGION: self.data.get("location"), + ATTR_SUBSCRIPTION_ID: self.data.get("SUBID"), + ATTR_SUBSCRIPTION_NAME: self.data.get("label"), + ATTR_VCPUS: self.data.get("vcpu_count"), } def update(self): diff --git a/homeassistant/components/binary_sensor/wemo.py b/homeassistant/components/binary_sensor/wemo.py index 1071aae50..281b27923 100644 --- a/homeassistant/components/binary_sensor/wemo.py +++ b/homeassistant/components/binary_sensor/wemo.py @@ -10,7 +10,7 @@ import requests from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.exceptions import PlatformNotReady -DEPENDENCIES = ['wemo'] +DEPENDENCIES = ["wemo"] _LOGGER = logging.getLogger(__name__) @@ -20,14 +20,16 @@ def setup_platform(hass, config, add_entities_callback, discovery_info=None): from pywemo import discovery if discovery_info is not None: - location = discovery_info['ssdp_description'] - mac = discovery_info['mac_address'] + location = discovery_info["ssdp_description"] + mac = discovery_info["mac_address"] try: device = discovery.device_from_description(location, mac) - except (requests.exceptions.ConnectionError, - requests.exceptions.Timeout) as err: - _LOGGER.error('Unable to access %s (%s)', location, err) + except ( + requests.exceptions.ConnectionError, + requests.exceptions.Timeout, + ) as err: + _LOGGER.error("Unable to access %s (%s)", location, err) raise PlatformNotReady if device: @@ -52,7 +54,7 @@ class WemoBinarySensor(BinarySensorDevice): updated = self.wemo.subscription_update(_type, _params) self._update(force_update=(not updated)) - if not hasattr(self, 'hass'): + if not hasattr(self, "hass"): return self.schedule_update_ha_state() @@ -84,5 +86,4 @@ class WemoBinarySensor(BinarySensorDevice): try: self._state = self.wemo.get_state(force_update) except AttributeError as err: - _LOGGER.warning( - "Could not update status for %s (%s)", self.name, err) + _LOGGER.warning("Could not update status for %s (%s)", self.name, err) diff --git a/homeassistant/components/binary_sensor/wink.py b/homeassistant/components/binary_sensor/wink.py index 1976e49f4..1c8d5e39d 100644 --- a/homeassistant/components/binary_sensor/wink.py +++ b/homeassistant/components/binary_sensor/wink.py @@ -12,22 +12,22 @@ from homeassistant.components.wink import DOMAIN, WinkDevice _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['wink'] +DEPENDENCIES = ["wink"] # These are the available sensors mapped to binary_sensor class SENSOR_TYPES = { - 'brightness': 'light', - 'capturing_audio': 'sound', - 'capturing_video': None, - 'co_detected': 'gas', - 'liquid_detected': 'moisture', - 'loudness': 'sound', - 'motion': 'motion', - 'noise': 'sound', - 'opened': 'opening', - 'presence': 'occupancy', - 'smoke_detected': 'smoke', - 'vibration': 'vibration', + "brightness": "light", + "capturing_audio": "sound", + "capturing_video": None, + "co_detected": "gas", + "liquid_detected": "moisture", + "loudness": "sound", + "motion": "motion", + "noise": "sound", + "opened": "opening", + "presence": "occupancy", + "smoke_detected": "smoke", + "vibration": "vibration", } @@ -37,48 +37,48 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for sensor in pywink.get_sensors(): _id = sensor.object_id() + sensor.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: if sensor.capability() in SENSOR_TYPES: add_entities([WinkBinarySensorDevice(sensor, hass)]) for key in pywink.get_keys(): _id = key.object_id() + key.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: add_entities([WinkBinarySensorDevice(key, hass)]) for sensor in pywink.get_smoke_and_co_detectors(): _id = sensor.object_id() + sensor.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: add_entities([WinkSmokeDetector(sensor, hass)]) for hub in pywink.get_hubs(): _id = hub.object_id() + hub.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: add_entities([WinkHub(hub, hass)]) for remote in pywink.get_remotes(): _id = remote.object_id() + remote.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: add_entities([WinkRemote(remote, hass)]) for button in pywink.get_buttons(): _id = button.object_id() + button.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: add_entities([WinkButton(button, hass)]) for gang in pywink.get_gangs(): _id = gang.object_id() + gang.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: add_entities([WinkGang(gang, hass)]) for door_bell_sensor in pywink.get_door_bells(): _id = door_bell_sensor.object_id() + door_bell_sensor.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: add_entities([WinkBinarySensorDevice(door_bell_sensor, hass)]) for camera_sensor in pywink.get_cameras(): _id = camera_sensor.object_id() + camera_sensor.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: try: if camera_sensor.capability() in SENSOR_TYPES: add_entities([WinkBinarySensorDevice(camera_sensor, hass)]) @@ -92,11 +92,11 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice): def __init__(self, wink, hass): """Initialize the Wink binary sensor.""" super().__init__(wink, hass) - if hasattr(self.wink, 'unit'): + if hasattr(self.wink, "unit"): self._unit_of_measurement = self.wink.unit() else: self._unit_of_measurement = None - if hasattr(self.wink, 'capability'): + if hasattr(self.wink, "capability"): self.capability = self.wink.capability() else: self.capability = None @@ -104,7 +104,7 @@ class WinkBinarySensorDevice(WinkDevice, BinarySensorDevice): @asyncio.coroutine def async_added_to_hass(self): """Call when entity is added to hass.""" - self.hass.data[DOMAIN]['entities']['binary_sensor'].append(self) + self.hass.data[DOMAIN]["entities"]["binary_sensor"].append(self) @property def is_on(self): @@ -129,7 +129,7 @@ class WinkSmokeDetector(WinkBinarySensorDevice): def device_state_attributes(self): """Return the device state attributes.""" _attributes = super().device_state_attributes - _attributes['test_activated'] = self.wink.test_activated() + _attributes["test_activated"] = self.wink.test_activated() return _attributes @@ -140,16 +140,16 @@ class WinkHub(WinkBinarySensorDevice): def device_state_attributes(self): """Return the device state attributes.""" _attributes = super().device_state_attributes - _attributes['update_needed'] = self.wink.update_needed() - _attributes['firmware_version'] = self.wink.firmware_version() - _attributes['pairing_mode'] = self.wink.pairing_mode() + _attributes["update_needed"] = self.wink.update_needed() + _attributes["firmware_version"] = self.wink.firmware_version() + _attributes["pairing_mode"] = self.wink.pairing_mode() _kidde_code = self.wink.kidde_radio_code() if _kidde_code is not None: # The service call to set the Kidde code # takes a string of 1s and 0s so it makes # sense to display it to the user that way _formatted_kidde_code = "{:b}".format(_kidde_code).zfill(8) - _attributes['kidde_radio_code'] = _formatted_kidde_code + _attributes["kidde_radio_code"] = _formatted_kidde_code return _attributes @@ -160,10 +160,10 @@ class WinkRemote(WinkBinarySensorDevice): def device_state_attributes(self): """Return the state attributes.""" _attributes = super().device_state_attributes - _attributes['button_on_pressed'] = self.wink.button_on_pressed() - _attributes['button_off_pressed'] = self.wink.button_off_pressed() - _attributes['button_up_pressed'] = self.wink.button_up_pressed() - _attributes['button_down_pressed'] = self.wink.button_down_pressed() + _attributes["button_on_pressed"] = self.wink.button_on_pressed() + _attributes["button_off_pressed"] = self.wink.button_off_pressed() + _attributes["button_up_pressed"] = self.wink.button_up_pressed() + _attributes["button_down_pressed"] = self.wink.button_down_pressed() return _attributes @property @@ -179,8 +179,8 @@ class WinkButton(WinkBinarySensorDevice): def device_state_attributes(self): """Return the device state attributes.""" _attributes = super().device_state_attributes - _attributes['pressed'] = self.wink.pressed() - _attributes['long_pressed'] = self.wink.long_pressed() + _attributes["pressed"] = self.wink.pressed() + _attributes["long_pressed"] = self.wink.long_pressed() return _attributes diff --git a/homeassistant/components/binary_sensor/wirelesstag.py b/homeassistant/components/binary_sensor/wirelesstag.py index 190b408ab..a57bed52e 100644 --- a/homeassistant/components/binary_sensor/wirelesstag.py +++ b/homeassistant/components/binary_sensor/wirelesstag.py @@ -10,70 +10,72 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.helpers.dispatcher import async_dispatcher_connect -from homeassistant.components.binary_sensor import ( - BinarySensorDevice, PLATFORM_SCHEMA) +from homeassistant.components.binary_sensor import BinarySensorDevice, PLATFORM_SCHEMA from homeassistant.components.wirelesstag import ( DOMAIN as WIRELESSTAG_DOMAIN, SIGNAL_BINARY_EVENT_UPDATE, - WirelessTagBaseSensor) -from homeassistant.const import ( - CONF_MONITORED_CONDITIONS, STATE_ON, STATE_OFF) + WirelessTagBaseSensor, +) +from homeassistant.const import CONF_MONITORED_CONDITIONS, STATE_ON, STATE_OFF import homeassistant.helpers.config_validation as cv -DEPENDENCIES = ['wirelesstag'] +DEPENDENCIES = ["wirelesstag"] _LOGGER = logging.getLogger(__name__) # On means in range, Off means out of range -SENSOR_PRESENCE = 'presence' +SENSOR_PRESENCE = "presence" # On means motion detected, Off means clear -SENSOR_MOTION = 'motion' +SENSOR_MOTION = "motion" # On means open, Off means closed -SENSOR_DOOR = 'door' +SENSOR_DOOR = "door" # On means temperature become too cold, Off means normal -SENSOR_COLD = 'cold' +SENSOR_COLD = "cold" # On means hot, Off means normal -SENSOR_HEAT = 'heat' +SENSOR_HEAT = "heat" # On means too dry (humidity), Off means normal -SENSOR_DRY = 'dry' +SENSOR_DRY = "dry" # On means too wet (humidity), Off means normal -SENSOR_WET = 'wet' +SENSOR_WET = "wet" # On means light detected, Off means no light -SENSOR_LIGHT = 'light' +SENSOR_LIGHT = "light" # On means moisture detected (wet), Off means no moisture (dry) -SENSOR_MOISTURE = 'moisture' +SENSOR_MOISTURE = "moisture" # On means tag battery is low, Off means normal -SENSOR_BATTERY = 'battery' +SENSOR_BATTERY = "battery" # Sensor types: Name, device_class, push notification type representing 'on', # attr to check SENSOR_TYPES = { - SENSOR_PRESENCE: 'Presence', - SENSOR_MOTION: 'Motion', - SENSOR_DOOR: 'Door', - SENSOR_COLD: 'Cold', - SENSOR_HEAT: 'Heat', - SENSOR_DRY: 'Too dry', - SENSOR_WET: 'Too wet', - SENSOR_LIGHT: 'Light', - SENSOR_MOISTURE: 'Leak', - SENSOR_BATTERY: 'Low Battery' + SENSOR_PRESENCE: "Presence", + SENSOR_MOTION: "Motion", + SENSOR_DOOR: "Door", + SENSOR_COLD: "Cold", + SENSOR_HEAT: "Heat", + SENSOR_DRY: "Too dry", + SENSOR_WET: "Too wet", + SENSOR_LIGHT: "Light", + SENSOR_MOISTURE: "Leak", + SENSOR_BATTERY: "Low Battery", } -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MONITORED_CONDITIONS, default=[]): - vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_MONITORED_CONDITIONS, default=[]): vol.All( + cv.ensure_list, [vol.In(SENSOR_TYPES)] + ) + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -86,8 +88,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): allowed_sensor_types = tag.supported_binary_events_types for sensor_type in config.get(CONF_MONITORED_CONDITIONS): if sensor_type in allowed_sensor_types: - sensors.append(WirelessTagBinarySensor(platform, tag, - sensor_type)) + sensors.append(WirelessTagBinarySensor(platform, tag, sensor_type)) add_entities(sensors, True) hass.add_job(platform.install_push_notifications, sensors) @@ -100,8 +101,7 @@ class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorDevice): """Initialize a binary sensor for a Wireless Sensor Tags.""" super().__init__(api, tag) self._sensor_type = sensor_type - self._name = '{0} {1}'.format(self._tag.name, - self.event.human_readable_name) + self._name = "{0} {1}".format(self._tag.name, self.event.human_readable_name) async def async_added_to_hass(self): """Register callbacks.""" @@ -111,7 +111,8 @@ class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorDevice): async_dispatcher_connect( self.hass, SIGNAL_BINARY_EVENT_UPDATE.format(tag_id, event_type, mac), - self._on_binary_event_callback) + self._on_binary_event_callback, + ) @property def is_on(self): @@ -144,5 +145,5 @@ class WirelessTagBinarySensor(WirelessTagBaseSensor, BinarySensorDevice): def _on_binary_event_callback(self, event): """Update state from arrived push notification.""" # state should be 'on' or 'off' - self._state = event.data.get('state') + self._state = event.data.get("state") self.async_schedule_update_ha_state() diff --git a/homeassistant/components/binary_sensor/workday.py b/homeassistant/components/binary_sensor/workday.py index 3fb2d7f7f..f33e3992b 100644 --- a/homeassistant/components/binary_sensor/workday.py +++ b/homeassistant/components/binary_sensor/workday.py @@ -17,45 +17,108 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['holidays==0.9.6'] +REQUIREMENTS = ["holidays==0.9.6"] # List of all countries currently supported by holidays # There seems to be no way to get the list out at runtime -ALL_COUNTRIES = ['Argentina', 'AR', 'Australia', 'AU', 'Austria', 'AT', - 'Belgium', 'BE', 'Canada', 'CA', 'Colombia', 'CO', 'Czech', - 'CZ', 'Denmark', 'DK', 'England', 'EuropeanCentralBank', - 'ECB', 'TAR', 'Finland', 'FI', 'France', 'FRA', 'Germany', - 'DE', 'Hungary', 'HU', 'India', 'IND', 'Ireland', - 'Isle of Man', 'Italy', 'IT', 'Japan', 'JP', 'Mexico', 'MX', - 'Netherlands', 'NL', 'NewZealand', 'NZ', 'Northern Ireland', - 'Norway', 'NO', 'Polish', 'PL', 'Portugal', 'PT', - 'PortugalExt', 'PTE', 'Scotland', 'Slovenia', 'SI', - 'Slovakia', 'SK', 'South Africa', 'ZA', 'Spain', 'ES', - 'Sweden', 'SE', 'Switzerland', 'CH', 'UnitedKingdom', 'UK', - 'UnitedStates', 'US', 'Wales'] -CONF_COUNTRY = 'country' -CONF_PROVINCE = 'province' -CONF_WORKDAYS = 'workdays' +ALL_COUNTRIES = [ + "Argentina", + "AR", + "Australia", + "AU", + "Austria", + "AT", + "Belgium", + "BE", + "Canada", + "CA", + "Colombia", + "CO", + "Czech", + "CZ", + "Denmark", + "DK", + "England", + "EuropeanCentralBank", + "ECB", + "TAR", + "Finland", + "FI", + "France", + "FRA", + "Germany", + "DE", + "Hungary", + "HU", + "India", + "IND", + "Ireland", + "Isle of Man", + "Italy", + "IT", + "Japan", + "JP", + "Mexico", + "MX", + "Netherlands", + "NL", + "NewZealand", + "NZ", + "Northern Ireland", + "Norway", + "NO", + "Polish", + "PL", + "Portugal", + "PT", + "PortugalExt", + "PTE", + "Scotland", + "Slovenia", + "SI", + "Slovakia", + "SK", + "South Africa", + "ZA", + "Spain", + "ES", + "Sweden", + "SE", + "Switzerland", + "CH", + "UnitedKingdom", + "UK", + "UnitedStates", + "US", + "Wales", +] +CONF_COUNTRY = "country" +CONF_PROVINCE = "province" +CONF_WORKDAYS = "workdays" # By default, Monday - Friday are workdays -DEFAULT_WORKDAYS = ['mon', 'tue', 'wed', 'thu', 'fri'] -CONF_EXCLUDES = 'excludes' +DEFAULT_WORKDAYS = ["mon", "tue", "wed", "thu", "fri"] +CONF_EXCLUDES = "excludes" # By default, public holidays, Saturdays and Sundays are excluded from workdays -DEFAULT_EXCLUDES = ['sat', 'sun', 'holiday'] -DEFAULT_NAME = 'Workday Sensor' -ALLOWED_DAYS = WEEKDAYS + ['holiday'] -CONF_OFFSET = 'days_offset' +DEFAULT_EXCLUDES = ["sat", "sun", "holiday"] +DEFAULT_NAME = "Workday Sensor" +ALLOWED_DAYS = WEEKDAYS + ["holiday"] +CONF_OFFSET = "days_offset" DEFAULT_OFFSET = 0 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES), - vol.Optional(CONF_EXCLUDES, default=DEFAULT_EXCLUDES): - vol.All(cv.ensure_list, [vol.In(ALLOWED_DAYS)]), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): vol.Coerce(int), - vol.Optional(CONF_PROVINCE): cv.string, - vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS): - vol.All(cv.ensure_list, [vol.In(ALLOWED_DAYS)]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_COUNTRY): vol.In(ALL_COUNTRIES), + vol.Optional(CONF_EXCLUDES, default=DEFAULT_EXCLUDES): vol.All( + cv.ensure_list, [vol.In(ALLOWED_DAYS)] + ), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_OFFSET, default=DEFAULT_OFFSET): vol.Coerce(int), + vol.Optional(CONF_PROVINCE): cv.string, + vol.Optional(CONF_WORKDAYS, default=DEFAULT_WORKDAYS): vol.All( + cv.ensure_list, [vol.In(ALLOWED_DAYS)] + ), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -75,25 +138,24 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if province: # 'state' and 'prov' are not interchangeable, so need to make # sure we use the right one - if (hasattr(obj_holidays, 'PROVINCES') and - province in obj_holidays.PROVINCES): - obj_holidays = getattr(holidays, country)( - prov=province, years=year) - elif (hasattr(obj_holidays, 'STATES') and - province in obj_holidays.STATES): - obj_holidays = getattr(holidays, country)( - state=province, years=year) + if hasattr(obj_holidays, "PROVINCES") and province in obj_holidays.PROVINCES: + obj_holidays = getattr(holidays, country)(prov=province, years=year) + elif hasattr(obj_holidays, "STATES") and province in obj_holidays.STATES: + obj_holidays = getattr(holidays, country)(state=province, years=year) else: - _LOGGER.error("There is no province/state %s in country %s", - province, country) + _LOGGER.error( + "There is no province/state %s in country %s", province, country + ) return False _LOGGER.debug("Found the following holidays for your configuration:") for date, name in sorted(obj_holidays.items()): _LOGGER.debug("%s %s", date, name) - add_entities([IsWorkdaySensor( - obj_holidays, workdays, excludes, days_offset, sensor_name)], True) + add_entities( + [IsWorkdaySensor(obj_holidays, workdays, excludes, days_offset, sensor_name)], + True, + ) def day_to_string(day): @@ -135,7 +197,7 @@ class IsWorkdaySensor(BinarySensorDevice): """Check if given day is in the includes list.""" if day in self._workdays: return True - if 'holiday' in self._workdays and now in self._obj_holidays: + if "holiday" in self._workdays and now in self._obj_holidays: return True return False @@ -144,7 +206,7 @@ class IsWorkdaySensor(BinarySensorDevice): """Check if given day is in the excludes list.""" if day in self._excludes: return True - if 'holiday' in self._excludes and now in self._obj_holidays: + if "holiday" in self._excludes and now in self._obj_holidays: return True return False @@ -156,7 +218,7 @@ class IsWorkdaySensor(BinarySensorDevice): return { CONF_WORKDAYS: self._workdays, CONF_EXCLUDES: self._excludes, - CONF_OFFSET: self._days_offset + CONF_OFFSET: self._days_offset, } @asyncio.coroutine diff --git a/homeassistant/components/binary_sensor/xiaomi_aqara.py b/homeassistant/components/binary_sensor/xiaomi_aqara.py index 730b662b9..77bd6cf2f 100644 --- a/homeassistant/components/binary_sensor/xiaomi_aqara.py +++ b/homeassistant/components/binary_sensor/xiaomi_aqara.py @@ -2,68 +2,80 @@ import logging from homeassistant.components.binary_sensor import BinarySensorDevice -from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY, - XiaomiDevice) +from homeassistant.components.xiaomi_aqara import PY_XIAOMI_GATEWAY, XiaomiDevice _LOGGER = logging.getLogger(__name__) -NO_CLOSE = 'no_close' -ATTR_OPEN_SINCE = 'Open since' +NO_CLOSE = "no_close" +ATTR_OPEN_SINCE = "Open since" -MOTION = 'motion' -NO_MOTION = 'no_motion' -ATTR_LAST_ACTION = 'last_action' -ATTR_NO_MOTION_SINCE = 'No motion since' +MOTION = "motion" +NO_MOTION = "no_motion" +ATTR_LAST_ACTION = "last_action" +ATTR_NO_MOTION_SINCE = "No motion since" -DENSITY = 'density' -ATTR_DENSITY = 'Density' +DENSITY = "density" +ATTR_DENSITY = "Density" def setup_platform(hass, config, add_entities, discovery_info=None): """Perform the setup for Xiaomi devices.""" devices = [] for (_, gateway) in hass.data[PY_XIAOMI_GATEWAY].gateways.items(): - for device in gateway.devices['binary_sensor']: - model = device['model'] - if model in ['motion', 'sensor_motion', 'sensor_motion.aq2']: + for device in gateway.devices["binary_sensor"]: + model = device["model"] + if model in ["motion", "sensor_motion", "sensor_motion.aq2"]: devices.append(XiaomiMotionSensor(device, hass, gateway)) - elif model in ['magnet', 'sensor_magnet', 'sensor_magnet.aq2']: + elif model in ["magnet", "sensor_magnet", "sensor_magnet.aq2"]: devices.append(XiaomiDoorSensor(device, gateway)) - elif model == 'sensor_wleak.aq1': + elif model == "sensor_wleak.aq1": devices.append(XiaomiWaterLeakSensor(device, gateway)) - elif model in ['smoke', 'sensor_smoke']: + elif model in ["smoke", "sensor_smoke"]: devices.append(XiaomiSmokeSensor(device, gateway)) - elif model in ['natgas', 'sensor_natgas']: + elif model in ["natgas", "sensor_natgas"]: devices.append(XiaomiNatgasSensor(device, gateway)) - elif model in ['switch', 'sensor_switch', - 'sensor_switch.aq2', 'sensor_switch.aq3']: - if 'proto' not in device or int(device['proto'][0:1]) == 1: - data_key = 'status' + elif model in [ + "switch", + "sensor_switch", + "sensor_switch.aq2", + "sensor_switch.aq3", + ]: + if "proto" not in device or int(device["proto"][0:1]) == 1: + data_key = "status" else: - data_key = 'button_0' - devices.append(XiaomiButton(device, 'Switch', data_key, - hass, gateway)) - elif model in ['86sw1', 'sensor_86sw1', 'sensor_86sw1.aq1']: - if 'proto' not in device or int(device['proto'][0:1]) == 1: - data_key = 'channel_0' + data_key = "button_0" + devices.append(XiaomiButton(device, "Switch", data_key, hass, gateway)) + elif model in ["86sw1", "sensor_86sw1", "sensor_86sw1.aq1"]: + if "proto" not in device or int(device["proto"][0:1]) == 1: + data_key = "channel_0" else: - data_key = 'button_0' - devices.append(XiaomiButton(device, 'Wall Switch', data_key, - hass, gateway)) - elif model in ['86sw2', 'sensor_86sw2', 'sensor_86sw2.aq1']: - if 'proto' not in device or int(device['proto'][0:1]) == 1: - data_key_left = 'channel_0' - data_key_right = 'channel_1' + data_key = "button_0" + devices.append( + XiaomiButton(device, "Wall Switch", data_key, hass, gateway) + ) + elif model in ["86sw2", "sensor_86sw2", "sensor_86sw2.aq1"]: + if "proto" not in device or int(device["proto"][0:1]) == 1: + data_key_left = "channel_0" + data_key_right = "channel_1" else: - data_key_left = 'button_0' - data_key_right = 'button_1' - devices.append(XiaomiButton(device, 'Wall Switch (Left)', - data_key_left, hass, gateway)) - devices.append(XiaomiButton(device, 'Wall Switch (Right)', - data_key_right, hass, gateway)) - devices.append(XiaomiButton(device, 'Wall Switch (Both)', - 'dual_channel', hass, gateway)) - elif model in ['cube', 'sensor_cube', 'sensor_cube.aqgl01']: + data_key_left = "button_0" + data_key_right = "button_1" + devices.append( + XiaomiButton( + device, "Wall Switch (Left)", data_key_left, hass, gateway + ) + ) + devices.append( + XiaomiButton( + device, "Wall Switch (Right)", data_key_right, hass, gateway + ) + ) + devices.append( + XiaomiButton( + device, "Wall Switch (Both)", "dual_channel", hass, gateway + ) + ) + elif model in ["cube", "sensor_cube", "sensor_cube.aqgl01"]: devices.append(XiaomiCube(device, hass, gateway)) add_entities(devices) @@ -96,7 +108,7 @@ class XiaomiBinarySensor(XiaomiDevice, BinarySensorDevice): def update(self): """Update the sensor state.""" - _LOGGER.debug('Updating xiaomi sensor by polling') + _LOGGER.debug("Updating xiaomi sensor by polling") self._get_from_hub(self._sid) @@ -106,8 +118,9 @@ class XiaomiNatgasSensor(XiaomiBinarySensor): def __init__(self, device, xiaomi_hub): """Initialize the XiaomiSmokeSensor.""" self._density = None - XiaomiBinarySensor.__init__(self, device, 'Natgas Sensor', xiaomi_hub, - 'alarm', 'gas') + XiaomiBinarySensor.__init__( + self, device, "Natgas Sensor", xiaomi_hub, "alarm", "gas" + ) @property def device_state_attributes(self): @@ -125,12 +138,12 @@ class XiaomiNatgasSensor(XiaomiBinarySensor): if value is None: return False - if value in ('1', '2'): + if value in ("1", "2"): if self._state: return False self._state = True return True - if value == '0': + if value == "0": if self._state: self._state = False return True @@ -144,12 +157,13 @@ class XiaomiMotionSensor(XiaomiBinarySensor): """Initialize the XiaomiMotionSensor.""" self._hass = hass self._no_motion_since = 0 - if 'proto' not in device or int(device['proto'][0:1]) == 1: - data_key = 'status' + if "proto" not in device or int(device["proto"][0:1]) == 1: + data_key = "status" else: - data_key = 'motion_status' - XiaomiBinarySensor.__init__(self, device, 'Motion Sensor', xiaomi_hub, - data_key, 'motion') + data_key = "motion_status" + XiaomiBinarySensor.__init__( + self, device, "Motion Sensor", xiaomi_hub, data_key, "motion" + ) @property def device_state_attributes(self): @@ -160,12 +174,13 @@ class XiaomiMotionSensor(XiaomiBinarySensor): def parse_data(self, data, raw_data): """Parse data sent by gateway.""" - if raw_data['cmd'] == 'heartbeat': + if raw_data["cmd"] == "heartbeat": _LOGGER.debug( - 'Skipping heartbeat of the motion sensor. ' - 'It can introduce an incorrect state because of a firmware ' - 'bug (https://github.com/home-assistant/home-assistant/pull/' - '11631#issuecomment-357507744).') + "Skipping heartbeat of the motion sensor. " + "It can introduce an incorrect state because of a firmware " + "bug (https://github.com/home-assistant/home-assistant/pull/" + "11631#issuecomment-357507744)." + ) return self._should_poll = False @@ -181,9 +196,7 @@ class XiaomiMotionSensor(XiaomiBinarySensor): if value == MOTION: self._should_poll = True if self.entity_id is not None: - self._hass.bus.fire('motion', { - 'entity_id': self.entity_id - }) + self._hass.bus.fire("motion", {"entity_id": self.entity_id}) self._no_motion_since = 0 if self._state: @@ -203,12 +216,13 @@ class XiaomiDoorSensor(XiaomiBinarySensor): def __init__(self, device, xiaomi_hub): """Initialize the XiaomiDoorSensor.""" self._open_since = 0 - if 'proto' not in device or int(device['proto'][0:1]) == 1: - data_key = 'status' + if "proto" not in device or int(device["proto"][0:1]) == 1: + data_key = "status" else: - data_key = 'window_status' - XiaomiBinarySensor.__init__(self, device, 'Door Window Sensor', - xiaomi_hub, data_key, 'opening') + data_key = "window_status" + XiaomiBinarySensor.__init__( + self, device, "Door Window Sensor", xiaomi_hub, data_key, "opening" + ) @property def device_state_attributes(self): @@ -228,13 +242,13 @@ class XiaomiDoorSensor(XiaomiBinarySensor): if value is None: return False - if value == 'open': + if value == "open": self._should_poll = True if self._state: return False self._state = True return True - if value == 'close': + if value == "close": self._open_since = 0 if self._state: self._state = False @@ -247,12 +261,13 @@ class XiaomiWaterLeakSensor(XiaomiBinarySensor): def __init__(self, device, xiaomi_hub): """Initialize the XiaomiWaterLeakSensor.""" - if 'proto' not in device or int(device['proto'][0:1]) == 1: - data_key = 'status' + if "proto" not in device or int(device["proto"][0:1]) == 1: + data_key = "status" else: - data_key = 'wleak_status' - XiaomiBinarySensor.__init__(self, device, 'Water Leak Sensor', - xiaomi_hub, data_key, 'moisture') + data_key = "wleak_status" + XiaomiBinarySensor.__init__( + self, device, "Water Leak Sensor", xiaomi_hub, data_key, "moisture" + ) def parse_data(self, data, raw_data): """Parse data sent by gateway.""" @@ -262,13 +277,13 @@ class XiaomiWaterLeakSensor(XiaomiBinarySensor): if value is None: return False - if value == 'leak': + if value == "leak": self._should_poll = True if self._state: return False self._state = True return True - if value == 'no_leak': + if value == "no_leak": if self._state: self._state = False return True @@ -281,8 +296,9 @@ class XiaomiSmokeSensor(XiaomiBinarySensor): def __init__(self, device, xiaomi_hub): """Initialize the XiaomiSmokeSensor.""" self._density = 0 - XiaomiBinarySensor.__init__(self, device, 'Smoke Sensor', xiaomi_hub, - 'alarm', 'smoke') + XiaomiBinarySensor.__init__( + self, device, "Smoke Sensor", xiaomi_hub, "alarm", "smoke" + ) @property def device_state_attributes(self): @@ -299,12 +315,12 @@ class XiaomiSmokeSensor(XiaomiBinarySensor): if value is None: return False - if value in ('1', '2'): + if value in ("1", "2"): if self._state: return False self._state = True return True - if value == '0': + if value == "0": if self._state: self._state = False return True @@ -318,8 +334,7 @@ class XiaomiButton(XiaomiBinarySensor): """Initialize the XiaomiButton.""" self._hass = hass self._last_action = None - XiaomiBinarySensor.__init__(self, device, name, xiaomi_hub, - data_key, None) + XiaomiBinarySensor.__init__(self, device, name, xiaomi_hub, data_key, None) @property def device_state_attributes(self): @@ -334,33 +349,32 @@ class XiaomiButton(XiaomiBinarySensor): if value is None: return False - if value == 'long_click_press': + if value == "long_click_press": self._state = True - click_type = 'long_click_press' - elif value == 'long_click_release': + click_type = "long_click_press" + elif value == "long_click_release": self._state = False - click_type = 'hold' - elif value == 'click': - click_type = 'single' - elif value == 'double_click': - click_type = 'double' - elif value == 'both_click': - click_type = 'both' - elif value == 'shake': - click_type = 'shake' - elif value in ['long_click', 'long_both_click']: + click_type = "hold" + elif value == "click": + click_type = "single" + elif value == "double_click": + click_type = "double" + elif value == "both_click": + click_type = "both" + elif value == "shake": + click_type = "shake" + elif value in ["long_click", "long_both_click"]: return False else: _LOGGER.warning("Unsupported click_type detected: %s", value) return False - self._hass.bus.fire('click', { - 'entity_id': self.entity_id, - 'click_type': click_type - }) + self._hass.bus.fire( + "click", {"entity_id": self.entity_id, "click_type": click_type} + ) self._last_action = click_type - if value in ['long_click_press', 'long_click_release']: + if value in ["long_click_press", "long_click_release"]: return True return False @@ -373,12 +387,11 @@ class XiaomiCube(XiaomiBinarySensor): self._hass = hass self._last_action = None self._state = False - if 'proto' not in device or int(device['proto'][0:1]) == 1: - data_key = 'status' + if "proto" not in device or int(device["proto"][0:1]) == 1: + data_key = "status" else: - data_key = 'cube_status' - XiaomiBinarySensor.__init__(self, device, 'Cube', xiaomi_hub, - data_key, None) + data_key = "cube_status" + XiaomiBinarySensor.__init__(self, device, "Cube", xiaomi_hub, data_key, None) @property def device_state_attributes(self): @@ -390,18 +403,21 @@ class XiaomiCube(XiaomiBinarySensor): def parse_data(self, data, raw_data): """Parse data sent by gateway.""" if self._data_key in data: - self._hass.bus.fire('cube_action', { - 'entity_id': self.entity_id, - 'action_type': data[self._data_key] - }) + self._hass.bus.fire( + "cube_action", + {"entity_id": self.entity_id, "action_type": data[self._data_key]}, + ) self._last_action = data[self._data_key] - if 'rotate' in data: - self._hass.bus.fire('cube_action', { - 'entity_id': self.entity_id, - 'action_type': 'rotate', - 'action_value': float(data['rotate'].replace(",", ".")) - }) - self._last_action = 'rotate' + if "rotate" in data: + self._hass.bus.fire( + "cube_action", + { + "entity_id": self.entity_id, + "action_type": "rotate", + "action_value": float(data["rotate"].replace(",", ".")), + }, + ) + self._last_action = "rotate" return True diff --git a/homeassistant/components/binary_sensor/zha.py b/homeassistant/components/binary_sensor/zha.py index aa07a673c..f2515e465 100644 --- a/homeassistant/components/binary_sensor/zha.py +++ b/homeassistant/components/binary_sensor/zha.py @@ -11,21 +11,20 @@ from homeassistant.components import zha _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['zha'] +DEPENDENCIES = ["zha"] # ZigBee Cluster Library Zone Type to Home Assistant device class CLASS_MAPPING = { - 0x000d: 'motion', - 0x0015: 'opening', - 0x0028: 'smoke', - 0x002a: 'moisture', - 0x002b: 'gas', - 0x002d: 'vibration', + 0x000d: "motion", + 0x0015: "opening", + 0x0028: "smoke", + 0x002a: "moisture", + 0x002b: "gas", + 0x002d: "vibration", } -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 Zigbee Home Automation binary sensors.""" discovery_info = zha.get_discovery_info(hass, discovery_info) if discovery_info is None: @@ -33,26 +32,25 @@ async def async_setup_platform(hass, config, async_add_entities, from zigpy.zcl.clusters.general import OnOff from zigpy.zcl.clusters.security import IasZone - if IasZone.cluster_id in discovery_info['in_clusters']: - await _async_setup_iaszone(hass, config, async_add_entities, - discovery_info) - elif OnOff.cluster_id in discovery_info['out_clusters']: - await _async_setup_remote(hass, config, async_add_entities, - discovery_info) + + if IasZone.cluster_id in discovery_info["in_clusters"]: + await _async_setup_iaszone(hass, config, async_add_entities, discovery_info) + elif OnOff.cluster_id in discovery_info["out_clusters"]: + await _async_setup_remote(hass, config, async_add_entities, discovery_info) -async def _async_setup_iaszone(hass, config, async_add_entities, - discovery_info): +async def _async_setup_iaszone(hass, config, async_add_entities, discovery_info): device_class = None from zigpy.zcl.clusters.security import IasZone - cluster = discovery_info['in_clusters'][IasZone.cluster_id] - if discovery_info['new_join']: + + cluster = discovery_info["in_clusters"][IasZone.cluster_id] + if discovery_info["new_join"]: await cluster.bind() ieee = cluster.endpoint.device.application.ieee - await cluster.write_attributes({'cie_addr': ieee}) + await cluster.write_attributes({"cie_addr": ieee}) try: - zone_type = await cluster['zone_type'] + zone_type = await cluster["zone_type"] device_class = CLASS_MAPPING.get(zone_type, None) except Exception: # pylint: disable=broad-except # If we fail to read from the device, use a non-specific class @@ -62,25 +60,33 @@ async def _async_setup_iaszone(hass, config, async_add_entities, async_add_entities([sensor], update_before_add=True) -async def _async_setup_remote(hass, config, async_add_entities, - discovery_info): +async def _async_setup_remote(hass, config, async_add_entities, discovery_info): remote = Remote(**discovery_info) - if discovery_info['new_join']: + if discovery_info["new_join"]: from zigpy.zcl.clusters.general import OnOff, LevelControl - out_clusters = discovery_info['out_clusters'] + + out_clusters = discovery_info["out_clusters"] if OnOff.cluster_id in out_clusters: cluster = out_clusters[OnOff.cluster_id] await zha.configure_reporting( - remote.entity_id, cluster, 0, min_report=0, max_report=600, - reportable_change=1 + remote.entity_id, + cluster, + 0, + min_report=0, + max_report=600, + reportable_change=1, ) if LevelControl.cluster_id in out_clusters: cluster = out_clusters[LevelControl.cluster_id] await zha.configure_reporting( - remote.entity_id, cluster, 0, min_report=1, max_report=600, - reportable_change=1 + remote.entity_id, + cluster, + 0, + min_report=1, + max_report=600, + reportable_change=1, ) async_add_entities([remote], update_before_add=True) @@ -96,6 +102,7 @@ class BinarySensor(zha.Entity, BinarySensorDevice): super().__init__(**kwargs) self._device_class = device_class from zigpy.zcl.clusters.security import IasZone + self._ias_zone_cluster = self._in_clusters[IasZone.cluster_id] @property @@ -130,13 +137,15 @@ class BinarySensor(zha.Entity, BinarySensorDevice): """Retrieve latest state.""" from zigpy.types.basic import uint16_t - result = await zha.safe_read(self._endpoint.ias_zone, - ['zone_status'], - allow_cache=False, - only_cache=(not self._initialized)) - state = result.get('zone_status', self._state) + result = await zha.safe_read( + self._endpoint.ias_zone, + ["zone_status"], + allow_cache=False, + only_cache=(not self._initialized), + ) + state = result.get("zone_status", self._state) if isinstance(state, (int, uint16_t)): - self._state = result.get('zone_status', self._state) & 3 + self._state = result.get("zone_status", self._state) & 3 class Remote(zha.Entity, BinarySensorDevice): @@ -205,6 +214,7 @@ class Remote(zha.Entity, BinarySensorDevice): self._state = False self._level = 0 from zigpy.zcl.clusters import general + self._out_listeners = { general.OnOff.cluster_id: self.OnOffListener(self), general.LevelControl.cluster_id: self.LevelListener(self), @@ -223,9 +233,9 @@ class Remote(zha.Entity, BinarySensorDevice): @property def device_state_attributes(self): """Return the device state attributes.""" - self._device_state_attributes.update({ - 'level': self._state and self._level or 0 - }) + self._device_state_attributes.update( + {"level": self._state and self._level or 0} + ) return self._device_state_attributes def move_level(self, change): @@ -252,6 +262,8 @@ class Remote(zha.Entity, BinarySensorDevice): async def async_update(self): """Retrieve latest state.""" from zigpy.zcl.clusters.general import OnOff + result = await zha.safe_read( - self._endpoint.out_clusters[OnOff.cluster_id], ['on_off']) - self._state = result.get('on_off', self._state) + self._endpoint.out_clusters[OnOff.cluster_id], ["on_off"] + ) + self._state = result.get("on_off", self._state) diff --git a/homeassistant/components/binary_sensor/zigbee.py b/homeassistant/components/binary_sensor/zigbee.py index 6b8925820..5623fc5be 100644 --- a/homeassistant/components/binary_sensor/zigbee.py +++ b/homeassistant/components/binary_sensor/zigbee.py @@ -8,24 +8,24 @@ import voluptuous as vol from homeassistant.components.binary_sensor import BinarySensorDevice from homeassistant.components.zigbee import ( - ZigBeeDigitalIn, ZigBeeDigitalInConfig, PLATFORM_SCHEMA) + ZigBeeDigitalIn, + ZigBeeDigitalInConfig, + PLATFORM_SCHEMA, +) -CONF_ON_STATE = 'on_state' +CONF_ON_STATE = "on_state" -DEFAULT_ON_STATE = 'high' -DEPENDENCIES = ['zigbee'] +DEFAULT_ON_STATE = "high" +DEPENDENCIES = ["zigbee"] -STATES = ['high', 'low'] +STATES = ["high", "low"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_ON_STATE): vol.In(STATES), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Optional(CONF_ON_STATE): vol.In(STATES)}) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the ZigBee binary sensor platform.""" - add_entities( - [ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))], True) + add_entities([ZigBeeBinarySensor(hass, ZigBeeDigitalInConfig(config))], True) class ZigBeeBinarySensor(ZigBeeDigitalIn, BinarySensorDevice): diff --git a/homeassistant/components/binary_sensor/zwave.py b/homeassistant/components/binary_sensor/zwave.py index 784a96d86..47e6a8146 100644 --- a/homeassistant/components/binary_sensor/zwave.py +++ b/homeassistant/components/binary_sensor/zwave.py @@ -10,10 +10,10 @@ import homeassistant.util.dt as dt_util from homeassistant.helpers.event import track_point_in_time from homeassistant.components import zwave from homeassistant.components.zwave import workaround -from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import -from homeassistant.components.binary_sensor import ( - DOMAIN, - BinarySensorDevice) +from homeassistant.components.zwave import ( + async_setup_platform +) # noqa pylint: disable=unused-import +from homeassistant.components.binary_sensor import DOMAIN, BinarySensorDevice _LOGGER = logging.getLogger(__name__) DEPENDENCIES = [] @@ -70,24 +70,25 @@ class ZWaveTriggerSensor(ZWaveBinarySensor): def update_properties(self): """Handle value changes for this entity's node.""" self._state = self.values.primary.data - _LOGGER.debug('off_delay=%s', self.values.off_delay) + _LOGGER.debug("off_delay=%s", self.values.off_delay) # Set re_arm_sec if off_delay is provided from the sensor if self.values.off_delay: - _LOGGER.debug('off_delay.data=%s', self.values.off_delay.data) + _LOGGER.debug("off_delay.data=%s", self.values.off_delay.data) self.re_arm_sec = self.values.off_delay.data * 8 # only allow this value to be true for re_arm secs if not self.hass: return self.invalidate_after = dt_util.utcnow() + datetime.timedelta( - seconds=self.re_arm_sec) + seconds=self.re_arm_sec + ) track_point_in_time( - self.hass, self.async_update_ha_state, - self.invalidate_after) + self.hass, self.async_update_ha_state, self.invalidate_after + ) @property def is_on(self): """Return true if movement has happened within the rearm time.""" - return self._state and \ - (self.invalidate_after is None or - self.invalidate_after > dt_util.utcnow()) + return self._state and ( + self.invalidate_after is None or self.invalidate_after > dt_util.utcnow() + ) diff --git a/homeassistant/components/blink.py b/homeassistant/components/blink.py index e84643711..df167dafb 100644 --- a/homeassistant/components/blink.py +++ b/homeassistant/components/blink.py @@ -10,34 +10,38 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, ATTR_FRIENDLY_NAME, ATTR_ARMED) + CONF_USERNAME, + CONF_PASSWORD, + ATTR_FRIENDLY_NAME, + ATTR_ARMED, +) from homeassistant.helpers import discovery -REQUIREMENTS = ['blinkpy==0.6.0'] +REQUIREMENTS = ["blinkpy==0.6.0"] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'blink' +DOMAIN = "blink" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -ARM_SYSTEM_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ARMED): cv.boolean -}) +ARM_SYSTEM_SCHEMA = vol.Schema({vol.Optional(ATTR_ARMED): cv.boolean}) -ARM_CAMERA_SCHEMA = vol.Schema({ - vol.Required(ATTR_FRIENDLY_NAME): cv.string, - vol.Optional(ATTR_ARMED): cv.boolean -}) +ARM_CAMERA_SCHEMA = vol.Schema( + {vol.Required(ATTR_FRIENDLY_NAME): cv.string, vol.Optional(ATTR_ARMED): cv.boolean} +) -SNAP_PICTURE_SCHEMA = vol.Schema({ - vol.Required(ATTR_FRIENDLY_NAME): cv.string -}) +SNAP_PICTURE_SCHEMA = vol.Schema({vol.Required(ATTR_FRIENDLY_NAME): cv.string}) class BlinkSystem: @@ -46,29 +50,32 @@ class BlinkSystem: def __init__(self, config_info): """Initialize the system.""" import blinkpy - self.blink = blinkpy.Blink(username=config_info[DOMAIN][CONF_USERNAME], - password=config_info[DOMAIN][CONF_PASSWORD]) + + self.blink = blinkpy.Blink( + username=config_info[DOMAIN][CONF_USERNAME], + password=config_info[DOMAIN][CONF_PASSWORD], + ) self.blink.setup_system() def setup(hass, config): """Set up Blink System.""" hass.data[DOMAIN] = BlinkSystem(config) - discovery.load_platform(hass, 'camera', DOMAIN, {}, config) - discovery.load_platform(hass, 'sensor', DOMAIN, {}, config) - discovery.load_platform(hass, 'binary_sensor', DOMAIN, {}, config) + discovery.load_platform(hass, "camera", DOMAIN, {}, config) + discovery.load_platform(hass, "sensor", DOMAIN, {}, config) + discovery.load_platform(hass, "binary_sensor", DOMAIN, {}, config) def snap_picture(call): """Take a picture.""" cameras = hass.data[DOMAIN].blink.cameras - name = call.data.get(ATTR_FRIENDLY_NAME, '') + name = call.data.get(ATTR_FRIENDLY_NAME, "") if name in cameras: cameras[name].snap_picture() def arm_camera(call): """Arm a camera.""" cameras = hass.data[DOMAIN].blink.cameras - name = call.data.get(ATTR_FRIENDLY_NAME, '') + name = call.data.get(ATTR_FRIENDLY_NAME, "") value = call.data.get(ATTR_ARMED, True) if name in cameras: cameras[name].set_motion_detect(value) @@ -80,10 +87,9 @@ def setup(hass, config): hass.data[DOMAIN].blink.refresh() hass.services.register( - DOMAIN, 'snap_picture', snap_picture, schema=SNAP_PICTURE_SCHEMA) - hass.services.register( - DOMAIN, 'arm_camera', arm_camera, schema=ARM_CAMERA_SCHEMA) - hass.services.register( - DOMAIN, 'arm_system', arm_system, schema=ARM_SYSTEM_SCHEMA) + DOMAIN, "snap_picture", snap_picture, schema=SNAP_PICTURE_SCHEMA + ) + hass.services.register(DOMAIN, "arm_camera", arm_camera, schema=ARM_CAMERA_SCHEMA) + hass.services.register(DOMAIN, "arm_system", arm_system, schema=ARM_SYSTEM_SCHEMA) return True diff --git a/homeassistant/components/bloomsky.py b/homeassistant/components/bloomsky.py index 00377b3f1..3fe08f0a4 100644 --- a/homeassistant/components/bloomsky.py +++ b/homeassistant/components/bloomsky.py @@ -19,19 +19,17 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) BLOOMSKY = None -BLOOMSKY_TYPE = ['camera', 'binary_sensor', 'sensor'] +BLOOMSKY_TYPE = ["camera", "binary_sensor", "sensor"] -DOMAIN = 'bloomsky' +DOMAIN = "bloomsky" # The BloomSky only updates every 5-8 minutes as per the API spec so there's # no point in polling the API more frequently MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_API_KEY): cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: vol.Schema({vol.Required(CONF_API_KEY): cv.string})}, extra=vol.ALLOW_EXTRA +) def setup(hass, config): @@ -54,7 +52,7 @@ class BloomSky: """Handle all communication with the BloomSky API.""" # API documentation at http://weatherlution.com/bloomsky-api/ - API_URL = 'https://api.bloomsky.com/api/skydata' + API_URL = "https://api.bloomsky.com/api/skydata" def __init__(self, api_key): """Initialize the BookSky.""" @@ -68,13 +66,12 @@ class BloomSky: """Use the API to retrieve a list of devices.""" _LOGGER.debug("Fetching BloomSky update") response = requests.get( - self.API_URL, headers={AUTHORIZATION: self._api_key}, timeout=10) + self.API_URL, headers={AUTHORIZATION: self._api_key}, timeout=10 + ) if response.status_code == 401: raise RuntimeError("Invalid API_KEY") elif response.status_code != 200: _LOGGER.error("Invalid HTTP response: %s", response.status_code) return # Create dictionary keyed off of the device unique id - self.devices.update({ - device['DeviceID']: device for device in response.json() - }) + self.devices.update({device["DeviceID"]: device for device in response.json()}) diff --git a/homeassistant/components/bmw_connected_drive/__init__.py b/homeassistant/components/bmw_connected_drive/__init__.py index b0ad1a867..0a4dcef82 100644 --- a/homeassistant/components/bmw_connected_drive/__init__.py +++ b/homeassistant/components/bmw_connected_drive/__init__.py @@ -14,43 +14,38 @@ from homeassistant.helpers import discovery from homeassistant.helpers.event import track_utc_time_change import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['bimmer_connected==0.5.1'] +REQUIREMENTS = ["bimmer_connected==0.5.1"] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'bmw_connected_drive' -CONF_REGION = 'region' -CONF_READ_ONLY = 'read_only' -ATTR_VIN = 'vin' +DOMAIN = "bmw_connected_drive" +CONF_REGION = "region" +CONF_READ_ONLY = "read_only" +ATTR_VIN = "vin" -ACCOUNT_SCHEMA = vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_REGION): vol.Any('north_america', 'china', - 'rest_of_world'), - vol.Optional(CONF_READ_ONLY, default=False): cv.boolean, -}) +ACCOUNT_SCHEMA = vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_REGION): vol.Any("north_america", "china", "rest_of_world"), + vol.Optional(CONF_READ_ONLY, default=False): cv.boolean, + } +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: { - cv.string: ACCOUNT_SCHEMA - }, -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema({DOMAIN: {cv.string: ACCOUNT_SCHEMA}}, extra=vol.ALLOW_EXTRA) -SERVICE_SCHEMA = vol.Schema({ - vol.Required(ATTR_VIN): cv.string, -}) +SERVICE_SCHEMA = vol.Schema({vol.Required(ATTR_VIN): cv.string}) -BMW_COMPONENTS = ['binary_sensor', 'device_tracker', 'lock', 'sensor'] +BMW_COMPONENTS = ["binary_sensor", "device_tracker", "lock", "sensor"] UPDATE_INTERVAL = 5 # in minutes -SERVICE_UPDATE_STATE = 'update_state' +SERVICE_UPDATE_STATE = "update_state" _SERVICE_MAP = { - 'light_flash': 'trigger_remote_light_flash', - 'sound_horn': 'trigger_remote_horn', - 'activate_air_conditioning': 'trigger_remote_air_conditioning', + "light_flash": "trigger_remote_light_flash", + "sound_horn": "trigger_remote_horn", + "activate_air_conditioning": "trigger_remote_air_conditioning", } @@ -78,16 +73,14 @@ def setup(hass, config: dict): return True -def setup_account(account_config: dict, hass, name: str) \ - -> 'BMWConnectedDriveAccount': +def setup_account(account_config: dict, hass, name: str) -> "BMWConnectedDriveAccount": """Set up a new BMWConnectedDriveAccount based on the config.""" username = account_config[CONF_USERNAME] password = account_config[CONF_PASSWORD] region = account_config[CONF_REGION] read_only = account_config[CONF_READ_ONLY] - _LOGGER.debug('Adding new account %s', name) - cd_account = BMWConnectedDriveAccount(username, password, region, name, - read_only) + _LOGGER.debug("Adding new account %s", name) + cd_account = BMWConnectedDriveAccount(username, password, region, name, read_only) def execute_service(call): """Execute a service for a vehicle. @@ -103,21 +96,23 @@ def setup_account(account_config: dict, hass, name: str) \ function_name = _SERVICE_MAP[call.service] function_call = getattr(vehicle.remote_services, function_name) function_call() + if not read_only: # register the remote services for service in _SERVICE_MAP: hass.services.register( - DOMAIN, service, - execute_service, - schema=SERVICE_SCHEMA) + DOMAIN, service, execute_service, schema=SERVICE_SCHEMA + ) # update every UPDATE_INTERVAL minutes, starting now # this should even out the load on the servers now = datetime.datetime.now() track_utc_time_change( - hass, cd_account.update, + hass, + cd_account.update, minute=range(now.minute % UPDATE_INTERVAL, 60, UPDATE_INTERVAL), - second=now.second) + second=now.second, + ) return cd_account @@ -125,8 +120,9 @@ def setup_account(account_config: dict, hass, name: str) \ class BMWConnectedDriveAccount: """Representation of a BMW vehicle.""" - def __init__(self, username: str, password: str, region_str: str, - name: str, read_only) -> None: + def __init__( + self, username: str, password: str, region_str: str, name: str, read_only + ) -> None: """Constructor.""" from bimmer_connected.account import ConnectedDriveAccount from bimmer_connected.country_selector import get_region_from_name @@ -143,15 +139,17 @@ class BMWConnectedDriveAccount: Notify all listeners about the update. """ - _LOGGER.debug('Updating vehicle state for account %s, ' - 'notifying %d listeners', - self.name, len(self._update_listeners)) + _LOGGER.debug( + "Updating vehicle state for account %s, " "notifying %d listeners", + self.name, + len(self._update_listeners), + ) try: self.account.update_vehicle_states() for listener in self._update_listeners: listener() except IOError as exception: - _LOGGER.error('Error updating the vehicle state.') + _LOGGER.error("Error updating the vehicle state.") _LOGGER.exception(exception) def add_update_listener(self, listener): diff --git a/homeassistant/components/browser.py b/homeassistant/components/browser.py index 041a0f9cd..8e56057a4 100644 --- a/homeassistant/components/browser.py +++ b/homeassistant/components/browser.py @@ -9,22 +9,26 @@ import voluptuous as vol DOMAIN = "browser" SERVICE_BROWSE_URL = "browse_url" -ATTR_URL = 'url' -ATTR_URL_DEFAULT = 'https://www.google.com' +ATTR_URL = "url" +ATTR_URL_DEFAULT = "https://www.google.com" -SERVICE_BROWSE_URL_SCHEMA = vol.Schema({ - # pylint: disable=no-value-for-parameter - vol.Required(ATTR_URL, default=ATTR_URL_DEFAULT): vol.Url(), -}) +SERVICE_BROWSE_URL_SCHEMA = vol.Schema( + { + # pylint: disable=no-value-for-parameter + vol.Required(ATTR_URL, default=ATTR_URL_DEFAULT): vol.Url() + } +) def setup(hass, config): """Listen for browse_url events.""" import webbrowser - hass.services.register(DOMAIN, SERVICE_BROWSE_URL, - lambda service: - webbrowser.open(service.data[ATTR_URL]), - schema=SERVICE_BROWSE_URL_SCHEMA) + hass.services.register( + DOMAIN, + SERVICE_BROWSE_URL, + lambda service: webbrowser.open(service.data[ATTR_URL]), + schema=SERVICE_BROWSE_URL_SCHEMA, + ) return True diff --git a/homeassistant/components/calendar/__init__.py b/homeassistant/components/calendar/__init__.py index 9d105fb02..335035c46 100644 --- a/homeassistant/components/calendar/__init__.py +++ b/homeassistant/components/calendar/__init__.py @@ -10,8 +10,7 @@ import re from aiohttp import web -from homeassistant.components.google import ( - CONF_OFFSET, CONF_DEVICE_ID, CONF_NAME) +from homeassistant.components.google import CONF_OFFSET, CONF_DEVICE_ID, CONF_NAME from homeassistant.const import STATE_OFF, STATE_ON from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa from homeassistant.helpers.config_validation import time_period_str @@ -24,19 +23,18 @@ from homeassistant.components import http _LOGGER = logging.getLogger(__name__) -DOMAIN = 'calendar' +DOMAIN = "calendar" -DEPENDENCIES = ['http'] +DEPENDENCIES = ["http"] -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" SCAN_INTERVAL = timedelta(seconds=60) async def async_setup(hass, config): """Track states and offer events for calendars.""" - component = EntityComponent( - _LOGGER, DOMAIN, hass, SCAN_INTERVAL, DOMAIN) + component = EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL, DOMAIN) hass.http.register_view(CalendarListView(component)) hass.http.register_view(CalendarEventView(component)) @@ -50,15 +48,16 @@ async def async_setup(hass, config): DEFAULT_CONF_TRACK_NEW = True -DEFAULT_CONF_OFFSET = '!!' +DEFAULT_CONF_OFFSET = "!!" def get_date(date): """Get the dateTime from date or dateTime as a local.""" - if 'date' in date: - return dt.start_of_local_day(dt.dt.datetime.combine( - dt.parse_date(date['date']), dt.dt.time.min)) - return dt.as_local(dt.parse_datetime(date['dateTime'])) + if "date" in date: + return dt.start_of_local_day( + dt.dt.datetime.combine(dt.parse_date(date["date"]), dt.dt.time.min) + ) + return dt.as_local(dt.parse_datetime(date["dateTime"])) class CalendarEventDevice(Entity): @@ -73,29 +72,31 @@ class CalendarEventDevice(Entity): self._name = data.get(CONF_NAME) self.dev_id = data.get(CONF_DEVICE_ID) self._offset = data.get(CONF_OFFSET, DEFAULT_CONF_OFFSET) - self.entity_id = generate_entity_id( - ENTITY_ID_FORMAT, self.dev_id, hass=hass) + self.entity_id = generate_entity_id(ENTITY_ID_FORMAT, self.dev_id, hass=hass) self._cal_data = { - 'all_day': False, - 'offset_time': dt.dt.timedelta(), - 'message': '', - 'start': None, - 'end': None, - 'location': '', - 'description': '', + "all_day": False, + "offset_time": dt.dt.timedelta(), + "message": "", + "start": None, + "end": None, + "location": "", + "description": "", } self.update() def offset_reached(self): """Have we reached the offset time specified in the event title.""" - if self._cal_data['start'] is None or \ - self._cal_data['offset_time'] == dt.dt.timedelta(): + if ( + self._cal_data["start"] is None + or self._cal_data["offset_time"] == dt.dt.timedelta() + ): return False - return self._cal_data['start'] + self._cal_data['offset_time'] <= \ - dt.now(self._cal_data['start'].tzinfo) + return self._cal_data["start"] + self._cal_data["offset_time"] <= dt.now( + self._cal_data["start"].tzinfo + ) @property def name(self): @@ -105,26 +106,26 @@ class CalendarEventDevice(Entity): @property def device_state_attributes(self): """Return the device state attributes.""" - start = self._cal_data.get('start', None) - end = self._cal_data.get('end', None) + start = self._cal_data.get("start", None) + end = self._cal_data.get("end", None) start = start.strftime(DATE_STR_FORMAT) if start is not None else None end = end.strftime(DATE_STR_FORMAT) if end is not None else None return { - 'message': self._cal_data.get('message', ''), - 'all_day': self._cal_data.get('all_day', False), - 'offset_reached': self.offset_reached(), - 'start_time': start, - 'end_time': end, - 'location': self._cal_data.get('location', None), - 'description': self._cal_data.get('description', None), + "message": self._cal_data.get("message", ""), + "all_day": self._cal_data.get("all_day", False), + "offset_reached": self.offset_reached(), + "start_time": start, + "end_time": end, + "location": self._cal_data.get("location", None), + "description": self._cal_data.get("description", None), } @property def state(self): """Return the state of the calendar event.""" - start = self._cal_data.get('start', None) - end = self._cal_data.get('end', None) + start = self._cal_data.get("start", None) + end = self._cal_data.get("end", None) if start is None or end is None: return STATE_OFF @@ -141,13 +142,13 @@ class CalendarEventDevice(Entity): def cleanup(self): """Cleanup any start/end listeners that were setup.""" self._cal_data = { - 'all_day': False, - 'offset_time': 0, - 'message': '', - 'start': None, - 'end': None, - 'location': None, - 'description': None + "all_day": False, + "offset_time": 0, + "message": "", + "start": None, + "end": None, + "location": None, + "description": None, } def update(self): @@ -161,44 +162,43 @@ class CalendarEventDevice(Entity): self.cleanup() return - start = get_date(self.data.event['start']) - end = get_date(self.data.event['end']) + start = get_date(self.data.event["start"]) + end = get_date(self.data.event["end"]) - summary = self.data.event.get('summary', '') + summary = self.data.event.get("summary", "") # check if we have an offset tag in the message # time is HH:MM or MM - reg = '{}([+-]?[0-9]{{0,2}}(:[0-9]{{0,2}})?)'.format(self._offset) + reg = "{}([+-]?[0-9]{{0,2}}(:[0-9]{{0,2}})?)".format(self._offset) search = re.search(reg, summary) if search and search.group(1): time = search.group(1) - if ':' not in time: - if time[0] == '+' or time[0] == '-': - time = '{}0:{}'.format(time[0], time[1:]) + if ":" not in time: + if time[0] == "+" or time[0] == "-": + time = "{}0:{}".format(time[0], time[1:]) else: - time = '0:{}'.format(time) + time = "0:{}".format(time) offset_time = time_period_str(time) - summary = (summary[:search.start()] + summary[search.end():]) \ - .strip() + summary = (summary[: search.start()] + summary[search.end() :]).strip() else: offset_time = dt.dt.timedelta() # default it # cleanup the string so we don't have a bunch of double+ spaces - self._cal_data['message'] = re.sub(' +', '', summary).strip() - self._cal_data['offset_time'] = offset_time - self._cal_data['location'] = self.data.event.get('location', '') - self._cal_data['description'] = self.data.event.get('description', '') - self._cal_data['start'] = start - self._cal_data['end'] = end - self._cal_data['all_day'] = 'date' in self.data.event['start'] + self._cal_data["message"] = re.sub(" +", "", summary).strip() + self._cal_data["offset_time"] = offset_time + self._cal_data["location"] = self.data.event.get("location", "") + self._cal_data["description"] = self.data.event.get("description", "") + self._cal_data["start"] = start + self._cal_data["end"] = end + self._cal_data["all_day"] = "date" in self.data.event["start"] class CalendarEventView(http.HomeAssistantView): """View to retrieve calendar content.""" - url = '/api/calendars/{entity_id}' - name = 'api:calendars:calendar' + url = "/api/calendars/{entity_id}" + name = "api:calendars:calendar" def __init__(self, component): """Initialize calendar view.""" @@ -207,8 +207,8 @@ class CalendarEventView(http.HomeAssistantView): async def get(self, request, entity_id): """Return calendar events.""" entity = self.component.get_entity(entity_id) - start = request.query.get('start') - end = request.query.get('end') + start = request.query.get("start") + end = request.query.get("end") if None in (start, end, entity): return web.Response(status=400) try: @@ -217,14 +217,15 @@ class CalendarEventView(http.HomeAssistantView): except (ValueError, AttributeError): return web.Response(status=400) event_list = await entity.async_get_events( - request.app['hass'], start_date, end_date) + request.app["hass"], start_date, end_date + ) return self.json(event_list) class CalendarListView(http.HomeAssistantView): """View to retrieve calendar list.""" - url = '/api/calendars' + url = "/api/calendars" name = "api:calendars" def __init__(self, component): @@ -233,14 +234,11 @@ class CalendarListView(http.HomeAssistantView): async def get(self, request): """Retrieve calendar list.""" - get_state = request.app['hass'].states.get + get_state = request.app["hass"].states.get calendar_list = [] for entity in self.component.entities: state = get_state(entity.entity_id) - calendar_list.append({ - "name": state.name, - "entity_id": entity.entity_id, - }) + calendar_list.append({"name": state.name, "entity_id": entity.entity_id}) - return self.json(sorted(calendar_list, key=lambda x: x['name'])) + return self.json(sorted(calendar_list, key=lambda x: x["name"])) diff --git a/homeassistant/components/calendar/caldav.py b/homeassistant/components/calendar/caldav.py index cb8874a81..cbcab2d72 100644 --- a/homeassistant/components/calendar/caldav.py +++ b/homeassistant/components/calendar/caldav.py @@ -11,40 +11,49 @@ import re import voluptuous as vol from homeassistant.components.calendar import ( - PLATFORM_SCHEMA, CalendarEventDevice, get_date) -from homeassistant.const import ( - CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME) + PLATFORM_SCHEMA, + CalendarEventDevice, + get_date, +) +from homeassistant.const import CONF_NAME, CONF_PASSWORD, CONF_URL, CONF_USERNAME import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle, dt -REQUIREMENTS = ['caldav==0.5.0'] +REQUIREMENTS = ["caldav==0.5.0"] _LOGGER = logging.getLogger(__name__) -CONF_DEVICE_ID = 'device_id' -CONF_CALENDARS = 'calendars' -CONF_CUSTOM_CALENDARS = 'custom_calendars' -CONF_CALENDAR = 'calendar' -CONF_SEARCH = 'search' +CONF_DEVICE_ID = "device_id" +CONF_CALENDARS = "calendars" +CONF_CUSTOM_CALENDARS = "custom_calendars" +CONF_CALENDAR = "calendar" +CONF_SEARCH = "search" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - # pylint: disable=no-value-for-parameter - vol.Required(CONF_URL): vol.Url(), - vol.Optional(CONF_CALENDARS, default=[]): - vol.All(cv.ensure_list, vol.Schema([ - cv.string - ])), - vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string, - vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string, - vol.Optional(CONF_CUSTOM_CALENDARS, default=[]): - vol.All(cv.ensure_list, vol.Schema([ - vol.Schema({ - vol.Required(CONF_CALENDAR): cv.string, - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_SEARCH): cv.string, - }) - ])) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + # pylint: disable=no-value-for-parameter + vol.Required(CONF_URL): vol.Url(), + vol.Optional(CONF_CALENDARS, default=[]): vol.All( + cv.ensure_list, vol.Schema([cv.string]) + ), + vol.Inclusive(CONF_USERNAME, "authentication"): cv.string, + vol.Inclusive(CONF_PASSWORD, "authentication"): cv.string, + vol.Optional(CONF_CUSTOM_CALENDARS, default=[]): vol.All( + cv.ensure_list, + vol.Schema( + [ + vol.Schema( + { + vol.Required(CONF_CALENDAR): cv.string, + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_SEARCH): cv.string, + } + ) + ] + ), + ), + } +) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) @@ -65,8 +74,9 @@ def setup_platform(hass, config, add_entities, disc_info=None): for calendar in list(calendars): # If a calendar name was given in the configuration, # ignore all the others - if (config.get(CONF_CALENDARS) - and calendar.name not in config.get(CONF_CALENDARS)): + if config.get(CONF_CALENDARS) and calendar.name not in config.get( + CONF_CALENDARS + ): _LOGGER.debug("Ignoring calendar '%s'", calendar.name) continue @@ -79,21 +89,19 @@ def setup_platform(hass, config, add_entities, disc_info=None): device_data = { CONF_NAME: cust_calendar.get(CONF_NAME), CONF_DEVICE_ID: "{} {}".format( - cust_calendar.get(CONF_CALENDAR), - cust_calendar.get(CONF_NAME)), + cust_calendar.get(CONF_CALENDAR), cust_calendar.get(CONF_NAME) + ), } calendar_devices.append( WebDavCalendarEventDevice( - hass, device_data, calendar, True, - cust_calendar.get(CONF_SEARCH))) + hass, device_data, calendar, True, cust_calendar.get(CONF_SEARCH) + ) + ) # Create a default calendar if there was no custom one if not config.get(CONF_CUSTOM_CALENDARS): - device_data = { - CONF_NAME: calendar.name, - CONF_DEVICE_ID: calendar.name, - } + device_data = {CONF_NAME: calendar.name, CONF_DEVICE_ID: calendar.name} calendar_devices.append( WebDavCalendarEventDevice(hass, device_data, calendar) ) @@ -104,8 +112,7 @@ def setup_platform(hass, config, add_entities, disc_info=None): class WebDavCalendarEventDevice(CalendarEventDevice): """A device for getting the next Task from a WebDav Calendar.""" - def __init__(self, hass, device_data, calendar, all_day=False, - search=None): + def __init__(self, hass, device_data, calendar, all_day=False, search=None): """Create the WebDav Calendar Event Device.""" self.data = WebDavCalendarData(calendar, all_day, search) super().__init__(hass, device_data) @@ -138,13 +145,14 @@ class WebDavCalendarData: async def async_get_events(self, hass, start_date, end_date): """Get all events in a specific time frame.""" # Get event list from the current calendar - vevent_list = await hass.async_add_job(self.calendar.date_search, - start_date, end_date) + vevent_list = await hass.async_add_job( + self.calendar.date_search, start_date, end_date + ) event_list = [] for event in vevent_list: vevent = event.instance.vevent uid = None - if hasattr(vevent, 'uid'): + if hasattr(vevent, "uid"): uid = vevent.uid.value data = { "uid": uid, @@ -155,8 +163,8 @@ class WebDavCalendarData: "description": self.get_attr_value(vevent, "description"), } - data['start'] = get_date(data['start']).isoformat() - data['end'] = get_date(data['end']).isoformat() + data["start"] = get_date(data["start"]).isoformat() + data["end"] = get_date(data["end"]).isoformat() event_list.append(data) @@ -168,28 +176,36 @@ class WebDavCalendarData: # We have to retrieve the results for the whole day as the server # won't return events that have already started results = self.calendar.date_search( - dt.start_of_local_day(), - dt.start_of_local_day() + timedelta(days=1) + dt.start_of_local_day(), dt.start_of_local_day() + timedelta(days=1) ) # dtstart can be a date or datetime depending if the event lasts a # whole day. Convert everything to datetime to be able to sort it - results.sort(key=lambda x: self.to_datetime( - x.instance.vevent.dtstart.value - )) + results.sort(key=lambda x: self.to_datetime(x.instance.vevent.dtstart.value)) - vevent = next(( - event.instance.vevent for event in results - if (self.is_matching(event.instance.vevent, self.search) - and (not self.is_all_day(event.instance.vevent) - or self.include_all_day) - and not self.is_over(event.instance.vevent))), None) + vevent = next( + ( + event.instance.vevent + for event in results + if ( + self.is_matching(event.instance.vevent, self.search) + and ( + not self.is_all_day(event.instance.vevent) + or self.include_all_day + ) + and not self.is_over(event.instance.vevent) + ) + ), + None, + ) # If no matching event could be found if vevent is None: _LOGGER.debug( "No matching event found in the %d results for %s", - len(results), self.calendar.name) + len(results), + self.calendar.name, + ) self.event = None return True @@ -199,7 +215,7 @@ class WebDavCalendarData: "start": self.get_hass_date(vevent.dtstart.value), "end": self.get_hass_date(self.get_end_date(vevent)), "location": self.get_attr_value(vevent, "location"), - "description": self.get_attr_value(vevent, "description") + "description": self.get_attr_value(vevent, "description"), } return True @@ -210,12 +226,14 @@ class WebDavCalendarData: return True pattern = re.compile(search) - return (hasattr(vevent, "summary") - and pattern.match(vevent.summary.value) - or hasattr(vevent, "location") - and pattern.match(vevent.location.value) - or hasattr(vevent, "description") - and pattern.match(vevent.description.value)) + return ( + hasattr(vevent, "summary") + and pattern.match(vevent.summary.value) + or hasattr(vevent, "location") + and pattern.match(vevent.location.value) + or hasattr(vevent, "description") + and pattern.match(vevent.description.value) + ) @staticmethod def is_all_day(vevent): diff --git a/homeassistant/components/calendar/demo.py b/homeassistant/components/calendar/demo.py index bf9d4abeb..7401e4395 100644 --- a/homeassistant/components/calendar/demo.py +++ b/homeassistant/components/calendar/demo.py @@ -15,17 +15,20 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo Calendar platform.""" calendar_data_future = DemoGoogleCalendarDataFuture() calendar_data_current = DemoGoogleCalendarDataCurrent() - add_entities([ - DemoGoogleCalendar(hass, calendar_data_future, { - CONF_NAME: 'Calendar 1', - CONF_DEVICE_ID: 'calendar_1', - }), - - DemoGoogleCalendar(hass, calendar_data_current, { - CONF_NAME: 'Calendar 2', - CONF_DEVICE_ID: 'calendar_2', - }), - ]) + add_entities( + [ + DemoGoogleCalendar( + hass, + calendar_data_future, + {CONF_NAME: "Calendar 1", CONF_DEVICE_ID: "calendar_1"}, + ), + DemoGoogleCalendar( + hass, + calendar_data_current, + {CONF_NAME: "Calendar 2", CONF_DEVICE_ID: "calendar_2"}, + ), + ] + ) class DemoGoogleCalendarData: @@ -41,9 +44,9 @@ class DemoGoogleCalendarData: async def async_get_events(self, hass, start_date, end_date): """Get all events in a specific time frame.""" event = copy.copy(self.event) - event['title'] = event['summary'] - event['start'] = get_date(event['start']).isoformat() - event['end'] = get_date(event['end']).isoformat() + event["title"] = event["summary"] + event["start"] = get_date(event["start"]).isoformat() + event["end"] = get_date(event["end"]).isoformat() return [event] @@ -52,17 +55,15 @@ class DemoGoogleCalendarDataFuture(DemoGoogleCalendarData): def __init__(self): """Set the event to a future event.""" - one_hour_from_now = dt_util.now() \ - + dt_util.dt.timedelta(minutes=30) + one_hour_from_now = dt_util.now() + dt_util.dt.timedelta(minutes=30) self.event = { - 'start': { - 'dateTime': one_hour_from_now.isoformat() + "start": {"dateTime": one_hour_from_now.isoformat()}, + "end": { + "dateTime": ( + one_hour_from_now + dt_util.dt.timedelta(minutes=60) + ).isoformat() }, - 'end': { - 'dateTime': (one_hour_from_now + dt_util.dt. - timedelta(minutes=60)).isoformat() - }, - 'summary': 'Future Event', + "summary": "Future Event", } @@ -71,17 +72,15 @@ class DemoGoogleCalendarDataCurrent(DemoGoogleCalendarData): def __init__(self): """Set the event data.""" - middle_of_event = dt_util.now() \ - - dt_util.dt.timedelta(minutes=30) + middle_of_event = dt_util.now() - dt_util.dt.timedelta(minutes=30) self.event = { - 'start': { - 'dateTime': middle_of_event.isoformat() + "start": {"dateTime": middle_of_event.isoformat()}, + "end": { + "dateTime": ( + middle_of_event + dt_util.dt.timedelta(minutes=60) + ).isoformat() }, - 'end': { - 'dateTime': (middle_of_event + dt_util.dt. - timedelta(minutes=60)).isoformat() - }, - 'summary': 'Current Event', + "summary": "Current Event", } diff --git a/homeassistant/components/calendar/google.py b/homeassistant/components/calendar/google.py index 041b98dc2..8ec4022c2 100644 --- a/homeassistant/components/calendar/google.py +++ b/homeassistant/components/calendar/google.py @@ -9,17 +9,22 @@ from datetime import timedelta from homeassistant.components.calendar import CalendarEventDevice from homeassistant.components.google import ( - CONF_CAL_ID, CONF_ENTITIES, CONF_TRACK, TOKEN_FILE, - CONF_IGNORE_AVAILABILITY, CONF_SEARCH, - GoogleCalendarService) + CONF_CAL_ID, + CONF_ENTITIES, + CONF_TRACK, + TOKEN_FILE, + CONF_IGNORE_AVAILABILITY, + CONF_SEARCH, + GoogleCalendarService, +) from homeassistant.util import Throttle, dt _LOGGER = logging.getLogger(__name__) DEFAULT_GOOGLE_SEARCH_PARAMS = { - 'orderBy': 'startTime', - 'maxResults': 5, - 'singleEvents': True, + "orderBy": "startTime", + "maxResults": 5, + "singleEvents": True, } MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) @@ -34,9 +39,15 @@ def setup_platform(hass, config, add_entities, disc_info=None): return calendar_service = GoogleCalendarService(hass.config.path(TOKEN_FILE)) - add_entities([GoogleCalendarEventDevice(hass, calendar_service, - disc_info[CONF_CAL_ID], data) - for data in disc_info[CONF_ENTITIES] if data[CONF_TRACK]]) + add_entities( + [ + GoogleCalendarEventDevice( + hass, calendar_service, disc_info[CONF_CAL_ID], data + ) + for data in disc_info[CONF_ENTITIES] + if data[CONF_TRACK] + ] + ) class GoogleCalendarEventDevice(CalendarEventDevice): @@ -44,9 +55,12 @@ class GoogleCalendarEventDevice(CalendarEventDevice): def __init__(self, hass, calendar_service, calendar, data): """Create the Calendar event device.""" - self.data = GoogleCalendarData(calendar_service, calendar, - data.get(CONF_SEARCH), - data.get(CONF_IGNORE_AVAILABILITY)) + self.data = GoogleCalendarData( + calendar_service, + calendar, + data.get(CONF_SEARCH), + data.get(CONF_IGNORE_AVAILABILITY), + ) super().__init__(hass, data) @@ -58,8 +72,7 @@ class GoogleCalendarEventDevice(CalendarEventDevice): class GoogleCalendarData: """Class to utilize calendar service object to get next event.""" - def __init__(self, calendar_service, calendar_id, search, - ignore_availability): + def __init__(self, calendar_service, calendar_id, search, ignore_availability): """Set up how we are going to search the google calendar.""" self.calendar_service = calendar_service self.calendar_id = calendar_id @@ -76,27 +89,26 @@ class GoogleCalendarData: _LOGGER.warning("Unable to connect to Google, using cached data") return False params = dict(DEFAULT_GOOGLE_SEARCH_PARAMS) - params['calendarId'] = self.calendar_id + params["calendarId"] = self.calendar_id if self.search: - params['q'] = self.search + params["q"] = self.search return service, params async def async_get_events(self, hass, start_date, end_date): """Get all events in a specific time frame.""" service, params = await hass.async_add_job(self._prepare_query) - params['timeMin'] = start_date.isoformat('T') - params['timeMax'] = end_date.isoformat('T') + params["timeMin"] = start_date.isoformat("T") + params["timeMax"] = end_date.isoformat("T") events = await hass.async_add_job(service.events) result = await hass.async_add_job(events.list(**params).execute) - items = result.get('items', []) + items = result.get("items", []) event_list = [] for item in items: - if (not self.ignore_availability - and 'transparency' in item.keys()): - if item['transparency'] == 'opaque': + if not self.ignore_availability and "transparency" in item.keys(): + if item["transparency"] == "opaque": event_list.append(item) else: event_list.append(item) @@ -106,18 +118,17 @@ class GoogleCalendarData: def update(self): """Get the latest data.""" service, params = self._prepare_query() - params['timeMin'] = dt.now().isoformat('T') + params["timeMin"] = dt.now().isoformat("T") events = service.events() result = events.list(**params).execute() - items = result.get('items', []) + items = result.get("items", []) new_event = None for item in items: - if (not self.ignore_availability - and 'transparency' in item.keys()): - if item['transparency'] == 'opaque': + if not self.ignore_availability and "transparency" in item.keys(): + if item["transparency"] == "opaque": new_event = item break else: diff --git a/homeassistant/components/calendar/todoist.py b/homeassistant/components/calendar/todoist.py index b5eaed4e6..7e92881a0 100644 --- a/homeassistant/components/calendar/todoist.py +++ b/homeassistant/components/calendar/todoist.py @@ -10,108 +10,134 @@ import logging import voluptuous as vol from homeassistant.components.calendar import ( - DOMAIN, PLATFORM_SCHEMA, CalendarEventDevice) + DOMAIN, + PLATFORM_SCHEMA, + CalendarEventDevice, +) from homeassistant.components.google import CONF_DEVICE_ID from homeassistant.const import CONF_ID, CONF_NAME, CONF_TOKEN import homeassistant.helpers.config_validation as cv from homeassistant.helpers.template import DATE_STR_FORMAT from homeassistant.util import Throttle, dt -REQUIREMENTS = ['todoist-python==7.0.17'] +REQUIREMENTS = ["todoist-python==7.0.17"] _LOGGER = logging.getLogger(__name__) -CONF_EXTRA_PROJECTS = 'custom_projects' -CONF_PROJECT_DUE_DATE = 'due_date_days' -CONF_PROJECT_LABEL_WHITELIST = 'labels' -CONF_PROJECT_WHITELIST = 'include_projects' +CONF_EXTRA_PROJECTS = "custom_projects" +CONF_PROJECT_DUE_DATE = "due_date_days" +CONF_PROJECT_LABEL_WHITELIST = "labels" +CONF_PROJECT_WHITELIST = "include_projects" # Calendar Platform: Does this calendar event last all day? -ALL_DAY = 'all_day' +ALL_DAY = "all_day" # Attribute: All tasks in this project -ALL_TASKS = 'all_tasks' +ALL_TASKS = "all_tasks" # Todoist API: "Completed" flag -- 1 if complete, else 0 -CHECKED = 'checked' +CHECKED = "checked" # Attribute: Is this task complete? -COMPLETED = 'completed' +COMPLETED = "completed" # Todoist API: What is this task about? # Service Call: What is this task about? -CONTENT = 'content' +CONTENT = "content" # Calendar Platform: Get a calendar event's description -DESCRIPTION = 'description' +DESCRIPTION = "description" # Calendar Platform: Used in the '_get_date()' method -DATETIME = 'dateTime' +DATETIME = "dateTime" # Service Call: When is this task due (in natural language)? -DUE_DATE_STRING = 'due_date_string' +DUE_DATE_STRING = "due_date_string" # Service Call: The language of DUE_DATE_STRING -DUE_DATE_LANG = 'due_date_lang' +DUE_DATE_LANG = "due_date_lang" # Service Call: The available options of DUE_DATE_LANG -DUE_DATE_VALID_LANGS = ['en', 'da', 'pl', 'zh', 'ko', 'de', - 'pt', 'ja', 'it', 'fr', 'sv', 'ru', - 'es', 'nl'] +DUE_DATE_VALID_LANGS = [ + "en", + "da", + "pl", + "zh", + "ko", + "de", + "pt", + "ja", + "it", + "fr", + "sv", + "ru", + "es", + "nl", +] # Attribute: When is this task due? # Service Call: When is this task due? -DUE_DATE = 'due_date' +DUE_DATE = "due_date" # Todoist API: Look up a task's due date -DUE_DATE_UTC = 'due_date_utc' +DUE_DATE_UTC = "due_date_utc" # Attribute: Is this task due today? -DUE_TODAY = 'due_today' +DUE_TODAY = "due_today" # Calendar Platform: When a calendar event ends -END = 'end' +END = "end" # Todoist API: Look up a Project/Label/Task ID -ID = 'id' +ID = "id" # Todoist API: Fetch all labels # Service Call: What are the labels attached to this task? -LABELS = 'labels' +LABELS = "labels" # Todoist API: "Name" value -NAME = 'name' +NAME = "name" # Attribute: Is this task overdue? -OVERDUE = 'overdue' +OVERDUE = "overdue" # Attribute: What is this task's priority? # Todoist API: Get a task's priority # Service Call: What is this task's priority? -PRIORITY = 'priority' +PRIORITY = "priority" # Todoist API: Look up the Project ID a Task belongs to -PROJECT_ID = 'project_id' +PROJECT_ID = "project_id" # Service Call: What Project do you want a Task added to? -PROJECT_NAME = 'project' +PROJECT_NAME = "project" # Todoist API: Fetch all Projects -PROJECTS = 'projects' +PROJECTS = "projects" # Calendar Platform: When does a calendar event start? -START = 'start' +START = "start" # Calendar Platform: What is the next calendar event about? -SUMMARY = 'summary' +SUMMARY = "summary" # Todoist API: Fetch all Tasks -TASKS = 'items' +TASKS = "items" -SERVICE_NEW_TASK = 'todoist_new_task' +SERVICE_NEW_TASK = "todoist_new_task" -NEW_TASK_SERVICE_SCHEMA = vol.Schema({ - vol.Required(CONTENT): cv.string, - vol.Optional(PROJECT_NAME, default='inbox'): vol.All(cv.string, vol.Lower), - vol.Optional(LABELS): cv.ensure_list_csv, - vol.Optional(PRIORITY): vol.All(vol.Coerce(int), vol.Range(min=1, max=4)), +NEW_TASK_SERVICE_SCHEMA = vol.Schema( + { + vol.Required(CONTENT): cv.string, + vol.Optional(PROJECT_NAME, default="inbox"): vol.All(cv.string, vol.Lower), + vol.Optional(LABELS): cv.ensure_list_csv, + vol.Optional(PRIORITY): vol.All(vol.Coerce(int), vol.Range(min=1, max=4)), + vol.Exclusive(DUE_DATE_STRING, "due_date"): cv.string, + vol.Optional(DUE_DATE_LANG): vol.All(cv.string, vol.In(DUE_DATE_VALID_LANGS)), + vol.Exclusive(DUE_DATE, "due_date"): cv.string, + } +) - vol.Exclusive(DUE_DATE_STRING, 'due_date'): cv.string, - vol.Optional(DUE_DATE_LANG): - vol.All(cv.string, vol.In(DUE_DATE_VALID_LANGS)), - vol.Exclusive(DUE_DATE, 'due_date'): cv.string, -}) - -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_TOKEN): cv.string, - vol.Optional(CONF_EXTRA_PROJECTS, default=[]): - vol.All(cv.ensure_list, vol.Schema([ - vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_PROJECT_DUE_DATE): vol.Coerce(int), - vol.Optional(CONF_PROJECT_WHITELIST, default=[]): - vol.All(cv.ensure_list, [vol.All(cv.string, vol.Lower)]), - vol.Optional(CONF_PROJECT_LABEL_WHITELIST, default=[]): - vol.All(cv.ensure_list, [vol.All(cv.string, vol.Lower)]) - }) - ])) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_TOKEN): cv.string, + vol.Optional(CONF_EXTRA_PROJECTS, default=[]): vol.All( + cv.ensure_list, + vol.Schema( + [ + vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_PROJECT_DUE_DATE): vol.Coerce(int), + vol.Optional(CONF_PROJECT_WHITELIST, default=[]): vol.All( + cv.ensure_list, [vol.All(cv.string, vol.Lower)] + ), + vol.Optional( + CONF_PROJECT_LABEL_WHITELIST, default=[] + ): vol.All(cv.ensure_list, [vol.All(cv.string, vol.Lower)]), + } + ) + ] + ), + ), + } +) MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=15) @@ -125,6 +151,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): label_id_lookup = {} from todoist.api import TodoistAPI + api = TodoistAPI(token) api.sync() @@ -140,13 +167,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for project in projects: # Project is an object, not a dict! # Because of that, we convert what we need to a dict. - project_data = { - CONF_NAME: project[NAME], - CONF_ID: project[ID] - } - project_devices.append( - TodoistProjectDevice(hass, project_data, labels, api) - ) + project_data = {CONF_NAME: project[NAME], CONF_ID: project[ID]} + project_devices.append(TodoistProjectDevice(hass, project_data, labels, api)) # Cache the names so we can easily look up name->ID. project_id_lookup[project[NAME].lower()] = project[ID] @@ -168,13 +190,19 @@ def setup_platform(hass, config, add_entities, discovery_info=None): project_name_filter = project.get(CONF_PROJECT_WHITELIST) project_id_filter = [ project_id_lookup[project_name.lower()] - for project_name in project_name_filter] + for project_name in project_name_filter + ] # Create the custom project and add it to the devices array. project_devices.append( TodoistProjectDevice( - hass, project, labels, api, project_due_date, - project_label_filter, project_id_filter + hass, + project, + labels, + api, + project_due_date, + project_label_filter, + project_id_filter, ) ) @@ -190,9 +218,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if LABELS in call.data: task_labels = call.data[LABELS] - label_ids = [ - label_id_lookup[label.lower()] - for label in task_labels] + label_ids = [label_id_lookup[label.lower()] for label in task_labels] item.update(labels=label_ids) if PRIORITY in call.data: @@ -211,34 +237,46 @@ def setup_platform(hass, config, add_entities, discovery_info=None): due_date = datetime(due.year, due.month, due.day) # Format it in the manner Todoist expects due_date = dt.as_utc(due_date) - date_format = '%Y-%m-%dT%H:%M' + date_format = "%Y-%m-%dT%H:%M" due_date = datetime.strftime(due_date, date_format) item.update(due_date_utc=due_date) # Commit changes api.commit() _LOGGER.debug("Created Todoist task: %s", call.data[CONTENT]) - hass.services.register(DOMAIN, SERVICE_NEW_TASK, handle_new_task, - schema=NEW_TASK_SERVICE_SCHEMA) + hass.services.register( + DOMAIN, SERVICE_NEW_TASK, handle_new_task, schema=NEW_TASK_SERVICE_SCHEMA + ) class TodoistProjectDevice(CalendarEventDevice): """A device for getting the next Task from a Todoist Project.""" - def __init__(self, hass, data, labels, token, - latest_task_due_date=None, whitelisted_labels=None, - whitelisted_projects=None): + def __init__( + self, + hass, + data, + labels, + token, + latest_task_due_date=None, + whitelisted_labels=None, + whitelisted_projects=None, + ): """Create the Todoist Calendar Event Device.""" self.data = TodoistProjectData( - data, labels, token, latest_task_due_date, - whitelisted_labels, whitelisted_projects + data, + labels, + token, + latest_task_due_date, + whitelisted_labels, + whitelisted_projects, ) # Set up the calendar side of things calendar_format = { CONF_NAME: data[CONF_NAME], # Set Entity ID to use the name so we can identify calendars - CONF_DEVICE_ID: data[CONF_NAME] + CONF_DEVICE_ID: data[CONF_NAME], } super().__init__(hass, calendar_format) @@ -250,7 +288,8 @@ class TodoistProjectDevice(CalendarEventDevice): # Set Todoist-specific data that can't easily be grabbed self._cal_data[ALL_TASKS] = [ - task[SUMMARY] for task in self.data.all_project_tasks] + task[SUMMARY] for task in self.data.all_project_tasks + ] def cleanup(self): """Clean up all calendar data.""" @@ -314,9 +353,15 @@ class TodoistProjectData: MIN_TIME_BETWEEN_UPDATES minutes. """ - def __init__(self, project_data, labels, api, - latest_task_due_date=None, whitelisted_labels=None, - whitelisted_projects=None): + def __init__( + self, + project_data, + labels, + api, + latest_task_due_date=None, + whitelisted_labels=None, + whitelisted_projects=None, + ): """Initialize a Todoist Project.""" self.event = None @@ -334,8 +379,7 @@ class TodoistProjectData: # The latest date a task can be due (for making lists of everything # due today, or everything due in the next week, for example). if latest_task_due_date is not None: - self._latest_due_date = dt.utcnow() + timedelta( - days=latest_task_due_date) + self._latest_due_date = dt.utcnow() + timedelta(days=latest_task_due_date) else: self._latest_due_date = None @@ -362,17 +406,16 @@ class TodoistProjectData: task[SUMMARY] = data[CONTENT] task[COMPLETED] = data[CHECKED] == 1 task[PRIORITY] = data[PRIORITY] - task[DESCRIPTION] = 'https://todoist.com/showTask?id={}'.format( - data[ID]) + task[DESCRIPTION] = "https://todoist.com/showTask?id={}".format(data[ID]) # All task Labels (optional parameter). task[LABELS] = [ - label[NAME].lower() for label in self._labels - if label[ID] in data[LABELS]] + label[NAME].lower() for label in self._labels if label[ID] in data[LABELS] + ] if self._label_whitelist and ( - not any(label in task[LABELS] - for label in self._label_whitelist)): + not any(label in task[LABELS] for label in self._label_whitelist) + ): # We're not on the whitelist, return invalid task. return None @@ -389,13 +432,14 @@ class TodoistProjectData: # Due dates are represented in RFC3339 format, in UTC. # Home Assistant exclusively uses UTC, so it'll # handle the conversion. - time_format = '%a %d %b %Y %H:%M:%S %z' + time_format = "%a %d %b %Y %H:%M:%S %z" # HASS' built-in parse time function doesn't like # Todoist's time format; strptime has to be used. task[END] = datetime.strptime(due_date, time_format) if self._latest_due_date is not None and ( - task[END] > self._latest_due_date): + task[END] > self._latest_due_date + ): # This task is out of range of our due date; # it shouldn't be counted. return None @@ -459,8 +503,7 @@ class TodoistProjectData: continue if proposed_event[END] is None: # No end time: - if event[END] is None and ( - proposed_event[PRIORITY] < event[PRIORITY]): + if event[END] is None and (proposed_event[PRIORITY] < event[PRIORITY]): # They also have no end time, # but we have a higher priority. event = proposed_event @@ -484,7 +527,8 @@ class TodoistProjectData: event = proposed_event continue elif proposed_event[PRIORITY] == event[PRIORITY] and ( - proposed_event[END] < event[END]): + proposed_event[END] < event[END] + ): event = proposed_event continue return event @@ -493,23 +537,25 @@ class TodoistProjectData: """Get all tasks in a specific time frame.""" if self._id is None: project_task_data = [ - task for task in self._api.state[TASKS] - if not self._project_id_whitelist or - task[PROJECT_ID] in self._project_id_whitelist] + task + for task in self._api.state[TASKS] + if not self._project_id_whitelist + or task[PROJECT_ID] in self._project_id_whitelist + ] else: project_task_data = self._api.projects.get_data(self._id)[TASKS] events = [] - time_format = '%a %d %b %Y %H:%M:%S %z' + time_format = "%a %d %b %Y %H:%M:%S %z" for task in project_task_data: - due_date = datetime.strptime(task['due_date_utc'], time_format) + due_date = datetime.strptime(task["due_date_utc"], time_format) if start_date < due_date < end_date: event = { - 'uid': task['id'], - 'title': task['content'], - 'start': due_date.isoformat(), - 'end': due_date.isoformat(), - 'allDay': True, + "uid": task["id"], + "title": task["content"], + "start": due_date.isoformat(), + "end": due_date.isoformat(), + "allDay": True, } events.append(event) return events @@ -519,9 +565,11 @@ class TodoistProjectData: """Get the latest data.""" if self._id is None: project_task_data = [ - task for task in self._api.state[TASKS] - if not self._project_id_whitelist or - task[PROJECT_ID] in self._project_id_whitelist] + task + for task in self._api.state[TASKS] + if not self._project_id_whitelist + or task[PROJECT_ID] in self._project_id_whitelist + ] else: project_task_data = self._api.projects.get_data(self._id)[TASKS] @@ -564,16 +612,14 @@ class TodoistProjectData: DATETIME: self.event[START].strftime(DATE_STR_FORMAT) } if self.event[END] is not None: - self.event[END] = { - DATETIME: self.event[END].strftime(DATE_STR_FORMAT) - } + self.event[END] = {DATETIME: self.event[END].strftime(DATE_STR_FORMAT)} else: # HASS gets cranky if a calendar event never ends # Let's set our "due date" to tomorrow self.event[END] = { - DATETIME: ( - datetime.utcnow() + timedelta(days=1) - ).strftime(DATE_STR_FORMAT) + DATETIME: (datetime.utcnow() + timedelta(days=1)).strftime( + DATE_STR_FORMAT + ) } _LOGGER.debug("Updated %s", self._name) return True diff --git a/homeassistant/components/camera/__init__.py b/homeassistant/components/camera/__init__.py index a8a486013..01b85c97a 100644 --- a/homeassistant/components/camera/__init__.py +++ b/homeassistant/components/camera/__init__.py @@ -19,8 +19,7 @@ import async_timeout import voluptuous as vol from homeassistant.core import callback -from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, \ - SERVICE_TURN_ON +from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON from homeassistant.exceptions import HomeAssistantError from homeassistant.loader import bind_hass from homeassistant.helpers.entity import Entity @@ -30,29 +29,29 @@ from homeassistant.components.http import HomeAssistantView, KEY_AUTHENTICATED from homeassistant.components import websocket_api import homeassistant.helpers.config_validation as cv -DOMAIN = 'camera' -DEPENDENCIES = ['http'] +DOMAIN = "camera" +DEPENDENCIES = ["http"] _LOGGER = logging.getLogger(__name__) -SERVICE_ENABLE_MOTION = 'enable_motion_detection' -SERVICE_DISABLE_MOTION = 'disable_motion_detection' -SERVICE_SNAPSHOT = 'snapshot' +SERVICE_ENABLE_MOTION = "enable_motion_detection" +SERVICE_DISABLE_MOTION = "disable_motion_detection" +SERVICE_SNAPSHOT = "snapshot" SCAN_INTERVAL = timedelta(seconds=30) -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" -ATTR_FILENAME = 'filename' +ATTR_FILENAME = "filename" -STATE_RECORDING = 'recording' -STATE_STREAMING = 'streaming' -STATE_IDLE = 'idle' +STATE_RECORDING = "recording" +STATE_STREAMING = "streaming" +STATE_IDLE = "idle" # Bitfield of features supported by the camera entity SUPPORT_ON_OFF = 1 -DEFAULT_CONTENT_TYPE = 'image/jpeg' -ENTITY_IMAGE_URL = '/api/camera_proxy/{0}?token={1}' +DEFAULT_CONTENT_TYPE = "image/jpeg" +ENTITY_IMAGE_URL = "/api/camera_proxy/{0}?token={1}" TOKEN_CHANGE_INTERVAL = timedelta(minutes=5) _RND = SystemRandom() @@ -60,19 +59,19 @@ _RND = SystemRandom() FALLBACK_STREAM_INTERVAL = 1 # seconds MIN_STREAM_INTERVAL = 0.5 # seconds -CAMERA_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, -}) +CAMERA_SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) -CAMERA_SERVICE_SNAPSHOT = CAMERA_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_FILENAME): cv.template -}) +CAMERA_SERVICE_SNAPSHOT = CAMERA_SERVICE_SCHEMA.extend( + {vol.Required(ATTR_FILENAME): cv.template} +) -WS_TYPE_CAMERA_THUMBNAIL = 'camera_thumbnail' -SCHEMA_WS_CAMERA_THUMBNAIL = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_CAMERA_THUMBNAIL, - vol.Required('entity_id'): cv.entity_id -}) +WS_TYPE_CAMERA_THUMBNAIL = "camera_thumbnail" +SCHEMA_WS_CAMERA_THUMBNAIL = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_CAMERA_THUMBNAIL, + vol.Required("entity_id"): cv.entity_id, + } +) @attr.s @@ -116,16 +115,14 @@ async def async_turn_on(hass, entity_id=None): def enable_motion_detection(hass, entity_id=None): """Enable Motion Detection.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else None - hass.async_add_job(hass.services.async_call( - DOMAIN, SERVICE_ENABLE_MOTION, data)) + hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_ENABLE_MOTION, data)) @bind_hass def disable_motion_detection(hass, entity_id=None): """Disable Motion Detection.""" data = {ATTR_ENTITY_ID: entity_id} if entity_id else None - hass.async_add_job(hass.services.async_call( - DOMAIN, SERVICE_DISABLE_MOTION, data)) + hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_DISABLE_MOTION, data)) @bind_hass @@ -135,8 +132,7 @@ def async_snapshot(hass, filename, entity_id=None): data = {ATTR_ENTITY_ID: entity_id} if entity_id else {} data[ATTR_FILENAME] = filename - hass.async_add_job(hass.services.async_call( - DOMAIN, SERVICE_SNAPSHOT, data)) + hass.async_add_job(hass.services.async_call(DOMAIN, SERVICE_SNAPSHOT, data)) @bind_hass @@ -151,7 +147,7 @@ async def async_get_image(hass, entity_id, timeout=10): if image: return Image(camera.content_type, image) - raise HomeAssistantError('Unable to get image') + raise HomeAssistantError("Unable to get image") @bind_hass @@ -168,18 +164,21 @@ async def async_get_still_stream(request, image_cb, content_type, interval): This method must be run in the event loop. """ response = web.StreamResponse() - response.content_type = ('multipart/x-mixed-replace; ' - 'boundary=--frameboundary') + response.content_type = "multipart/x-mixed-replace; " "boundary=--frameboundary" await response.prepare(request) async def write_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') + 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 @@ -207,29 +206,29 @@ def _get_camera_from_entity_id(hass, entity_id): component = hass.data.get(DOMAIN) if component is None: - raise HomeAssistantError('Camera component not set up') + raise HomeAssistantError("Camera component not set up") camera = component.get_entity(entity_id) if camera is None: - raise HomeAssistantError('Camera not found') + raise HomeAssistantError("Camera not found") if not camera.is_on: - raise HomeAssistantError('Camera is off') + raise HomeAssistantError("Camera is off") return camera async def async_setup(hass, config): """Set up the camera component.""" - component = hass.data[DOMAIN] = \ - EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) + component = hass.data[DOMAIN] = EntityComponent( + _LOGGER, DOMAIN, hass, SCAN_INTERVAL + ) hass.http.register_view(CameraImageView(component)) hass.http.register_view(CameraMjpegStream(component)) hass.components.websocket_api.async_register_command( - WS_TYPE_CAMERA_THUMBNAIL, websocket_camera_thumbnail, - SCHEMA_WS_CAMERA_THUMBNAIL + WS_TYPE_CAMERA_THUMBNAIL, websocket_camera_thumbnail, SCHEMA_WS_CAMERA_THUMBNAIL ) await component.async_setup(config) @@ -241,28 +240,22 @@ async def async_setup(hass, config): entity.async_update_token() hass.async_add_job(entity.async_update_ha_state()) - hass.helpers.event.async_track_time_interval( - update_tokens, TOKEN_CHANGE_INTERVAL) + hass.helpers.event.async_track_time_interval(update_tokens, TOKEN_CHANGE_INTERVAL) component.async_register_entity_service( - SERVICE_ENABLE_MOTION, CAMERA_SERVICE_SCHEMA, - 'async_enable_motion_detection' + SERVICE_ENABLE_MOTION, CAMERA_SERVICE_SCHEMA, "async_enable_motion_detection" ) component.async_register_entity_service( - SERVICE_DISABLE_MOTION, CAMERA_SERVICE_SCHEMA, - 'async_disable_motion_detection' + SERVICE_DISABLE_MOTION, CAMERA_SERVICE_SCHEMA, "async_disable_motion_detection" ) component.async_register_entity_service( - SERVICE_TURN_OFF, CAMERA_SERVICE_SCHEMA, - 'async_turn_off' + SERVICE_TURN_OFF, CAMERA_SERVICE_SCHEMA, "async_turn_off" ) component.async_register_entity_service( - SERVICE_TURN_ON, CAMERA_SERVICE_SCHEMA, - 'async_turn_on' + SERVICE_TURN_ON, CAMERA_SERVICE_SCHEMA, "async_turn_on" ) component.async_register_entity_service( - SERVICE_SNAPSHOT, CAMERA_SERVICE_SNAPSHOT, - async_handle_snapshot_service + SERVICE_SNAPSHOT, CAMERA_SERVICE_SNAPSHOT, async_handle_snapshot_service ) return True @@ -345,8 +338,9 @@ class Camera(Entity): This method must be run in the event loop. """ - return await async_get_still_stream(request, self.async_camera_image, - self.content_type, interval) + 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. @@ -410,18 +404,16 @@ class Camera(Entity): @property def state_attributes(self): """Return the camera state attributes.""" - attrs = { - 'access_token': self.access_tokens[-1], - } + attrs = {"access_token": self.access_tokens[-1]} if self.model: - attrs['model_name'] = self.model + attrs["model_name"] = self.model if self.brand: - attrs['brand'] = self.brand + attrs["brand"] = self.brand if self.motion_detection_enabled: - attrs['motion_detection'] = self.motion_detection_enabled + attrs["motion_detection"] = self.motion_detection_enabled return attrs @@ -429,8 +421,8 @@ class Camera(Entity): def async_update_token(self): """Update the used token.""" self.access_tokens.append( - hashlib.sha256( - _RND.getrandbits(256).to_bytes(32, 'little')).hexdigest()) + hashlib.sha256(_RND.getrandbits(256).to_bytes(32, "little")).hexdigest() + ) class CameraView(HomeAssistantView): @@ -449,14 +441,16 @@ class CameraView(HomeAssistantView): if camera is None: raise web.HTTPNotFound() - authenticated = (request[KEY_AUTHENTICATED] or - request.query.get('token') in camera.access_tokens) + authenticated = ( + request[KEY_AUTHENTICATED] + or request.query.get("token") in camera.access_tokens + ) if not authenticated: raise web.HTTPUnauthorized() if not camera.is_on: - _LOGGER.debug('Camera is off.') + _LOGGER.debug("Camera is off.") raise web.HTTPServiceUnavailable() return await self.handle(request, camera) @@ -469,18 +463,17 @@ class CameraView(HomeAssistantView): class CameraImageView(CameraView): """Camera view to serve an image.""" - url = '/api/camera_proxy/{entity_id}' - name = 'api:camera:image' + url = "/api/camera_proxy/{entity_id}" + name = "api:camera:image" async def handle(self, request, camera): """Serve camera image.""" with suppress(asyncio.CancelledError, asyncio.TimeoutError): - with async_timeout.timeout(10, loop=request.app['hass'].loop): + with async_timeout.timeout(10, loop=request.app["hass"].loop): image = await camera.async_camera_image() if image: - return web.Response(body=image, - content_type=camera.content_type) + return web.Response(body=image, content_type=camera.content_type) raise web.HTTPInternalServerError() @@ -488,21 +481,22 @@ class CameraImageView(CameraView): class CameraMjpegStream(CameraView): """Camera View to serve an MJPEG stream.""" - url = '/api/camera_proxy_stream/{entity_id}' - name = 'api:camera:stream' + url = "/api/camera_proxy_stream/{entity_id}" + name = "api:camera:stream" async def handle(self, request, camera): """Serve camera stream, possibly with interval.""" - interval = request.query.get('interval') + interval = request.query.get("interval") if interval is None: return await camera.handle_async_mjpeg_stream(request) try: # Compose camera stream from stills - interval = float(request.query.get('interval')) + interval = float(request.query.get("interval")) if interval < MIN_STREAM_INTERVAL: - raise ValueError("Stream interval must be be > {}" - .format(MIN_STREAM_INTERVAL)) + raise ValueError( + "Stream interval must be be > {}".format(MIN_STREAM_INTERVAL) + ) return await camera.handle_async_still_stream(request, interval) except ValueError: raise web.HTTPBadRequest() @@ -514,19 +508,26 @@ def websocket_camera_thumbnail(hass, connection, msg): Async friendly. """ + async def send_camera_still(): """Send a camera still.""" try: - image = await async_get_image(hass, msg['entity_id']) - connection.send_message_outside(websocket_api.result_message( - msg['id'], { - 'content_type': image.content_type, - 'content': base64.b64encode(image.content).decode('utf-8') - } - )) + image = await async_get_image(hass, msg["entity_id"]) + connection.send_message_outside( + websocket_api.result_message( + msg["id"], + { + "content_type": image.content_type, + "content": base64.b64encode(image.content).decode("utf-8"), + }, + ) + ) except HomeAssistantError: - connection.send_message_outside(websocket_api.error_message( - msg['id'], 'image_fetch_failed', 'Unable to fetch image')) + connection.send_message_outside( + websocket_api.error_message( + msg["id"], "image_fetch_failed", "Unable to fetch image" + ) + ) hass.async_add_job(send_camera_still()) @@ -537,24 +538,21 @@ async def async_handle_snapshot_service(camera, service): filename = service.data[ATTR_FILENAME] filename.hass = hass - snapshot_file = filename.async_render( - variables={ATTR_ENTITY_ID: camera}) + snapshot_file = filename.async_render(variables={ATTR_ENTITY_ID: camera}) # check if we allow to access to that file if not hass.config.is_allowed_path(snapshot_file): - _LOGGER.error( - "Can't write %s, no access to path!", snapshot_file) + _LOGGER.error("Can't write %s, no access to path!", snapshot_file) return image = await camera.async_camera_image() def _write_image(to_file, image_data): """Executor helper to write image.""" - with open(to_file, 'wb') as img_file: + with open(to_file, "wb") as img_file: img_file.write(image_data) try: - await hass.async_add_executor_job( - _write_image, snapshot_file, image) + await hass.async_add_executor_job(_write_image, snapshot_file, image) except OSError as err: _LOGGER.error("Can't write image to file: %s", err) diff --git a/homeassistant/components/camera/abode.py b/homeassistant/components/camera/abode.py index fbab1620a..ca5941505 100644 --- a/homeassistant/components/camera/abode.py +++ b/homeassistant/components/camera/abode.py @@ -15,7 +15,7 @@ from homeassistant.components.camera import Camera from homeassistant.util import Throttle -DEPENDENCIES = ['abode'] +DEPENDENCIES = ["abode"] MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90) @@ -58,7 +58,8 @@ class AbodeCamera(AbodeDevice, Camera): self.hass.async_add_job( self._data.abode.events.add_timeline_callback, - self._event, self._capture_callback + self._event, + self._capture_callback, ) def capture(self): @@ -75,8 +76,7 @@ class AbodeCamera(AbodeDevice, Camera): """Attempt to download the most recent capture.""" if self._device.image_url: try: - self._response = requests.get( - self._device.image_url, stream=True) + self._response = requests.get(self._device.image_url, stream=True) self._response.raise_for_status() except requests.HTTPError as err: diff --git a/homeassistant/components/camera/amcrest.py b/homeassistant/components/camera/amcrest.py index 9f4b84db2..5daaea91f 100644 --- a/homeassistant/components/camera/amcrest.py +++ b/homeassistant/components/camera/amcrest.py @@ -7,23 +7,23 @@ https://home-assistant.io/components/camera.amcrest/ import asyncio import logging -from homeassistant.components.amcrest import ( - DATA_AMCREST, STREAM_SOURCE_LIST, TIMEOUT) +from homeassistant.components.amcrest import DATA_AMCREST, STREAM_SOURCE_LIST, TIMEOUT from homeassistant.components.camera import Camera from homeassistant.components.ffmpeg import DATA_FFMPEG from homeassistant.const import CONF_NAME from homeassistant.helpers.aiohttp_client import ( - async_get_clientsession, async_aiohttp_proxy_web, - async_aiohttp_proxy_stream) + async_get_clientsession, + async_aiohttp_proxy_web, + async_aiohttp_proxy_stream, +) -DEPENDENCIES = ['amcrest', 'ffmpeg'] +DEPENDENCIES = ["amcrest", "ffmpeg"] _LOGGER = logging.getLogger(__name__) @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 an Amcrest IP Camera.""" if discovery_info is None: return @@ -61,16 +61,17 @@ class AmcrestCam(Camera): def handle_async_mjpeg_stream(self, request): """Return an MJPEG stream.""" # The snapshot implementation is handled by the parent class - if self._stream_source == STREAM_SOURCE_LIST['snapshot']: + if self._stream_source == STREAM_SOURCE_LIST["snapshot"]: yield from super().handle_async_mjpeg_stream(request) return - if self._stream_source == STREAM_SOURCE_LIST['mjpeg']: + if self._stream_source == STREAM_SOURCE_LIST["mjpeg"]: # stream an MJPEG image stream directly from the camera websession = async_get_clientsession(self.hass) streaming_url = self._camera.mjpeg_url(typeno=self._resolution) stream_coro = websession.get( - streaming_url, auth=self._token, timeout=TIMEOUT) + streaming_url, auth=self._token, timeout=TIMEOUT + ) yield from async_aiohttp_proxy_web(self.hass, request, stream_coro) @@ -81,11 +82,15 @@ class AmcrestCam(Camera): streaming_url = self._camera.rtsp_url(typeno=self._resolution) stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) yield from stream.open_camera( - streaming_url, extra_cmd=self._ffmpeg_arguments) + streaming_url, extra_cmd=self._ffmpeg_arguments + ) yield from async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self.hass, + request, + stream, + "multipart/x-mixed-replace;boundary=ffserver", + ) yield from stream.close() @property diff --git a/homeassistant/components/camera/arlo.py b/homeassistant/components/camera/arlo.py index af931c74c..701c3586e 100644 --- a/homeassistant/components/camera/arlo.py +++ b/homeassistant/components/camera/arlo.py @@ -10,8 +10,7 @@ import voluptuous as vol from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -from homeassistant.components.arlo import ( - DEFAULT_BRAND, DATA_ARLO, SIGNAL_UPDATE_ARLO) +from homeassistant.components.arlo import DEFAULT_BRAND, DATA_ARLO, SIGNAL_UPDATE_ARLO from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.components.ffmpeg import DATA_FFMPEG from homeassistant.const import ATTR_BATTERY_LEVEL @@ -20,31 +19,27 @@ from homeassistant.helpers.dispatcher import async_dispatcher_connect _LOGGER = logging.getLogger(__name__) -ARLO_MODE_ARMED = 'armed' -ARLO_MODE_DISARMED = 'disarmed' +ARLO_MODE_ARMED = "armed" +ARLO_MODE_DISARMED = "disarmed" -ATTR_BRIGHTNESS = 'brightness' -ATTR_FLIPPED = 'flipped' -ATTR_MIRRORED = 'mirrored' -ATTR_MOTION = 'motion_detection_sensitivity' -ATTR_POWERSAVE = 'power_save_mode' -ATTR_SIGNAL_STRENGTH = 'signal_strength' -ATTR_UNSEEN_VIDEOS = 'unseen_videos' -ATTR_LAST_REFRESH = 'last_refresh' +ATTR_BRIGHTNESS = "brightness" +ATTR_FLIPPED = "flipped" +ATTR_MIRRORED = "mirrored" +ATTR_MOTION = "motion_detection_sensitivity" +ATTR_POWERSAVE = "power_save_mode" +ATTR_SIGNAL_STRENGTH = "signal_strength" +ATTR_UNSEEN_VIDEOS = "unseen_videos" +ATTR_LAST_REFRESH = "last_refresh" -CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' +CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments" -DEPENDENCIES = ['arlo', 'ffmpeg'] +DEPENDENCIES = ["arlo", "ffmpeg"] -POWERSAVE_MODE_MAPPING = { - 1: 'best_battery_life', - 2: 'optimized', - 3: 'best_video' -} +POWERSAVE_MODE_MAPPING = {1: "best_battery_life", 2: "optimized", 3: "best_video"} -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -78,8 +73,7 @@ class ArloCam(Camera): async def async_added_to_hass(self): """Register callbacks.""" - async_dispatcher_connect( - self.hass, SIGNAL_UPDATE_ARLO, self._update_callback) + async_dispatcher_connect(self.hass, SIGNAL_UPDATE_ARLO, self._update_callback) @callback def _update_callback(self): @@ -89,21 +83,21 @@ class ArloCam(Camera): async def handle_async_mjpeg_stream(self, request): """Generate an HTTP MJPEG stream from the camera.""" from haffmpeg import CameraMjpeg + video = self._camera.last_video if not video: - error_msg = \ - 'Video not found for {0}. Is it older than {1} days?'.format( - self.name, self._camera.min_days_vdo_cache) + error_msg = "Video not found for {0}. Is it older than {1} days?".format( + self.name, self._camera.min_days_vdo_cache + ) _LOGGER.error(error_msg) return stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) - await stream.open_camera( - video.video_url, extra_cmd=self._ffmpeg_arguments) + await stream.open_camera(video.video_url, extra_cmd=self._ffmpeg_arguments) await async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self.hass, request, stream, "multipart/x-mixed-replace;boundary=ffserver" + ) await stream.close() @property @@ -115,17 +109,21 @@ class ArloCam(Camera): def device_state_attributes(self): """Return the state attributes.""" return { - name: value for name, value in ( + name: value + for name, value in ( (ATTR_BATTERY_LEVEL, self._camera.battery_level), (ATTR_BRIGHTNESS, self._camera.brightness), (ATTR_FLIPPED, self._camera.flip_state), (ATTR_MIRRORED, self._camera.mirror_state), (ATTR_MOTION, self._camera.motion_detection_sensitivity), - (ATTR_POWERSAVE, POWERSAVE_MODE_MAPPING.get( - self._camera.powersave_mode)), + ( + ATTR_POWERSAVE, + POWERSAVE_MODE_MAPPING.get(self._camera.powersave_mode), + ), (ATTR_SIGNAL_STRENGTH, self._camera.signal_strength), (ATTR_UNSEEN_VIDEOS, self._camera.unseen_videos), - ) if value is not None + ) + if value is not None } @property diff --git a/homeassistant/components/camera/august.py b/homeassistant/components/camera/august.py index dcce5e135..07ed3379f 100644 --- a/homeassistant/components/camera/august.py +++ b/homeassistant/components/camera/august.py @@ -11,7 +11,7 @@ import requests from homeassistant.components.august import DATA_AUGUST, DEFAULT_TIMEOUT from homeassistant.components.camera import Camera -DEPENDENCIES = ['august'] +DEPENDENCIES = ["august"] SCAN_INTERVAL = timedelta(seconds=5) @@ -57,12 +57,12 @@ class AugustCamera(Camera): @property def brand(self): """Return the camera brand.""" - return 'August' + return "August" @property def model(self): """Return the camera model.""" - return 'Doorbell' + return "Doorbell" def camera_image(self): """Return bytes of camera image.""" @@ -70,7 +70,8 @@ class AugustCamera(Camera): if self._image_url is not latest.image_url: self._image_url = latest.image_url - self._image_content = requests.get(self._image_url, - timeout=self._timeout).content + self._image_content = requests.get( + self._image_url, timeout=self._timeout + ).content return self._image_content diff --git a/homeassistant/components/camera/axis.py b/homeassistant/components/camera/axis.py index 4227cca7e..b9e122120 100644 --- a/homeassistant/components/camera/axis.py +++ b/homeassistant/components/camera/axis.py @@ -7,24 +7,33 @@ https://home-assistant.io/components/camera.axis/ import logging from homeassistant.components.camera.mjpeg import ( - CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera) + CONF_MJPEG_URL, + CONF_STILL_IMAGE_URL, + MjpegCamera, +) from homeassistant.const import ( - CONF_AUTHENTICATION, CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_PORT, - CONF_USERNAME, HTTP_DIGEST_AUTHENTICATION) + CONF_AUTHENTICATION, + CONF_HOST, + CONF_NAME, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, + HTTP_DIGEST_AUTHENTICATION, +) from homeassistant.helpers.dispatcher import dispatcher_connect _LOGGER = logging.getLogger(__name__) -DOMAIN = 'axis' +DOMAIN = "axis" DEPENDENCIES = [DOMAIN] def _get_image_url(host, port, mode): """Set the URL to get the image.""" - if mode == 'mjpeg': - return 'http://{}:{}/axis-cgi/mjpg/video.cgi'.format(host, port) - if mode == 'single': - return 'http://{}:{}/axis-cgi/jpg/image.cgi'.format(host, port) + if mode == "mjpeg": + return "http://{}:{}/axis-cgi/mjpg/video.cgi".format(host, port) + if mode == "single": + return "http://{}:{}/axis-cgi/jpg/image.cgi".format(host, port) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -34,15 +43,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): CONF_USERNAME: discovery_info[CONF_USERNAME], CONF_PASSWORD: discovery_info[CONF_PASSWORD], CONF_MJPEG_URL: _get_image_url( - discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]), - 'mjpeg'), + discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]), "mjpeg" + ), CONF_STILL_IMAGE_URL: _get_image_url( - discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]), - 'single'), + discovery_info[CONF_HOST], str(discovery_info[CONF_PORT]), "single" + ), CONF_AUTHENTICATION: HTTP_DIGEST_AUTHENTICATION, } - add_entities([AxisCamera( - hass, camera_config, str(discovery_info[CONF_PORT]))]) + add_entities([AxisCamera(hass, camera_config, str(discovery_info[CONF_PORT]))]) class AxisCamera(MjpegCamera): @@ -53,9 +61,10 @@ class AxisCamera(MjpegCamera): super().__init__(config) self.port = port dispatcher_connect( - hass, DOMAIN + '_' + config[CONF_NAME] + '_new_ip', self._new_ip) + hass, DOMAIN + "_" + config[CONF_NAME] + "_new_ip", self._new_ip + ) def _new_ip(self, host): """Set new IP for video stream.""" - self._mjpeg_url = _get_image_url(host, self.port, 'mjpeg') - self._still_image_url = _get_image_url(host, self.port, 'single') + self._mjpeg_url = _get_image_url(host, self.port, "mjpeg") + self._still_image_url = _get_image_url(host, self.port, "single") diff --git a/homeassistant/components/camera/blink.py b/homeassistant/components/camera/blink.py index 217849138..9c0dadb31 100644 --- a/homeassistant/components/camera/blink.py +++ b/homeassistant/components/camera/blink.py @@ -15,7 +15,7 @@ from homeassistant.util import Throttle _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['blink'] +DEPENDENCIES = ["blink"] MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=90) diff --git a/homeassistant/components/camera/bloomsky.py b/homeassistant/components/camera/bloomsky.py index 01e20e3cc..51c2e0eaf 100644 --- a/homeassistant/components/camera/bloomsky.py +++ b/homeassistant/components/camera/bloomsky.py @@ -10,7 +10,7 @@ import requests from homeassistant.components.camera import Camera -DEPENDENCIES = ['bloomsky'] +DEPENDENCIES = ["bloomsky"] def setup_platform(hass, config, add_entities, discovery_info=None): @@ -26,8 +26,8 @@ class BloomSkyCamera(Camera): def __init__(self, bs, device): """Initialize access to the BloomSky camera images.""" super(BloomSkyCamera, self).__init__() - self._name = device['DeviceName'] - self._id = device['DeviceID'] + self._name = device["DeviceName"] + self._id = device["DeviceID"] self._bloomsky = bs self._url = "" self._last_url = "" @@ -40,7 +40,7 @@ class BloomSkyCamera(Camera): def camera_image(self): """Update the camera's image if it has changed.""" try: - self._url = self._bloomsky.devices[self._id]['Data']['ImageURL'] + self._url = self._bloomsky.devices[self._id]["Data"]["ImageURL"] self._bloomsky.refresh_devices() # If the URL hasn't changed then the image hasn't changed. if self._url != self._last_url: diff --git a/homeassistant/components/camera/canary.py b/homeassistant/components/camera/canary.py index 9031c27b1..ef99f3134 100644 --- a/homeassistant/components/camera/canary.py +++ b/homeassistant/components/camera/canary.py @@ -17,17 +17,17 @@ from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream from homeassistant.util import Throttle -CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' +CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments" -DEPENDENCIES = ['canary', 'ffmpeg'] +DEPENDENCIES = ["canary", "ffmpeg"] _LOGGER = logging.getLogger(__name__) MIN_TIME_BETWEEN_SESSION_RENEW = timedelta(seconds=90) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -39,8 +39,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for device in location.devices: if device.is_online: devices.append( - CanaryCamera(hass, data, location, device, DEFAULT_TIMEOUT, - config.get(CONF_FFMPEG_ARGUMENTS))) + CanaryCamera( + hass, + data, + location, + device, + DEFAULT_TIMEOUT, + config.get(CONF_FFMPEG_ARGUMENTS), + ) + ) add_entities(devices, True) @@ -81,11 +88,16 @@ class CanaryCamera(Camera): self.renew_live_stream_session() from haffmpeg import ImageFrame, IMAGE_JPEG + ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop) - image = yield from asyncio.shield(ffmpeg.get_image( - self._live_stream_session.live_stream_url, - output_format=IMAGE_JPEG, - extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop) + image = yield from asyncio.shield( + ffmpeg.get_image( + self._live_stream_session.live_stream_url, + output_format=IMAGE_JPEG, + extra_cmd=self._ffmpeg_arguments, + ), + loop=self.hass.loop, + ) return image @asyncio.coroutine @@ -95,18 +107,18 @@ class CanaryCamera(Camera): return from haffmpeg import CameraMjpeg + stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) yield from stream.open_camera( - self._live_stream_session.live_stream_url, - extra_cmd=self._ffmpeg_arguments) + self._live_stream_session.live_stream_url, extra_cmd=self._ffmpeg_arguments + ) yield from async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self.hass, request, stream, "multipart/x-mixed-replace;boundary=ffserver" + ) yield from stream.close() @Throttle(MIN_TIME_BETWEEN_SESSION_RENEW) def renew_live_stream_session(self): """Renew live stream session.""" - self._live_stream_session = self._data.get_live_stream_session( - self._device) + self._live_stream_session = self._data.get_live_stream_session(self._device) diff --git a/homeassistant/components/camera/demo.py b/homeassistant/components/camera/demo.py index f950edb5c..053633682 100644 --- a/homeassistant/components/camera/demo.py +++ b/homeassistant/components/camera/demo.py @@ -12,12 +12,9 @@ from homeassistant.components.camera import Camera, SUPPORT_ON_OFF _LOGGER = logging.getLogger(__name__) -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 Demo camera platform.""" - async_add_entities([ - DemoCamera('Demo camera') - ]) + async_add_entities([DemoCamera("Demo camera")]) class DemoCamera(Camera): @@ -36,10 +33,10 @@ class DemoCamera(Camera): self._images_index = (self._images_index + 1) % 4 image_path = os.path.join( - os.path.dirname(__file__), - 'demo_{}.jpg'.format(self._images_index)) - _LOGGER.debug('Loading camera_image: %s', image_path) - with open(image_path, 'rb') as file: + os.path.dirname(__file__), "demo_{}.jpg".format(self._images_index) + ) + _LOGGER.debug("Loading camera_image: %s", image_path) + with open(image_path, "rb") as file: return file.read() @property diff --git a/homeassistant/components/camera/doorbird.py b/homeassistant/components/camera/doorbird.py index 7af3e7634..263f5e452 100644 --- a/homeassistant/components/camera/doorbird.py +++ b/homeassistant/components/camera/doorbird.py @@ -15,7 +15,7 @@ from homeassistant.components.camera import Camera from homeassistant.components.doorbird import DOMAIN as DOORBIRD_DOMAIN from homeassistant.helpers.aiohttp_client import async_get_clientsession -DEPENDENCIES = ['doorbird'] +DEPENDENCIES = ["doorbird"] _CAMERA_LAST_VISITOR = "{} Last Ring" _CAMERA_LAST_MOTION = "{} Last Motion" @@ -28,25 +28,29 @@ _TIMEOUT = 10 # seconds @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 DoorBird camera platform.""" for doorstation in hass.data[DOORBIRD_DOMAIN]: device = doorstation.device - async_add_entities([ - DoorBirdCamera( - device.live_image_url, - _CAMERA_LIVE.format(doorstation.name), - _LIVE_INTERVAL), - DoorBirdCamera( - device.history_image_url(1, 'doorbell'), - _CAMERA_LAST_VISITOR.format(doorstation.name), - _LAST_VISITOR_INTERVAL), - DoorBirdCamera( - device.history_image_url(1, 'motionsensor'), - _CAMERA_LAST_MOTION.format(doorstation.name), - _LAST_MOTION_INTERVAL), - ]) + async_add_entities( + [ + DoorBirdCamera( + device.live_image_url, + _CAMERA_LIVE.format(doorstation.name), + _LIVE_INTERVAL, + ), + DoorBirdCamera( + device.history_image_url(1, "doorbell"), + _CAMERA_LAST_VISITOR.format(doorstation.name), + _LAST_VISITOR_INTERVAL, + ), + DoorBirdCamera( + device.history_image_url(1, "motionsensor"), + _CAMERA_LAST_MOTION.format(doorstation.name), + _LAST_MOTION_INTERVAL, + ), + ] + ) class DoorBirdCamera(Camera): diff --git a/homeassistant/components/camera/familyhub.py b/homeassistant/components/camera/familyhub.py index f3dd8b6d0..f8e261d71 100644 --- a/homeassistant/components/camera/familyhub.py +++ b/homeassistant/components/camera/familyhub.py @@ -16,20 +16,22 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['python-family-hub-local==0.0.2'] +REQUIREMENTS = ["python-family-hub-local==0.0.2"] -DEFAULT_NAME = 'FamilyHub Camera' +DEFAULT_NAME = "FamilyHub Camera" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_IP_ADDRESS): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_IP_ADDRESS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): 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 Family Hub Camera.""" from pyfamilyhublocal import FamilyHubCam + address = config.get(CONF_IP_ADDRESS) name = config.get(CONF_NAME) diff --git a/homeassistant/components/camera/ffmpeg.py b/homeassistant/components/camera/ffmpeg.py index c45818869..346377d4b 100644 --- a/homeassistant/components/camera/ffmpeg.py +++ b/homeassistant/components/camera/ffmpeg.py @@ -12,25 +12,28 @@ import voluptuous as vol from homeassistant.const import CONF_NAME from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.components.ffmpeg import ( - DATA_FFMPEG, CONF_INPUT, CONF_EXTRA_ARGUMENTS) + DATA_FFMPEG, + CONF_INPUT, + CONF_EXTRA_ARGUMENTS, +) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.aiohttp_client import ( - async_aiohttp_proxy_stream) +from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['ffmpeg'] -DEFAULT_NAME = 'FFmpeg' +DEPENDENCIES = ["ffmpeg"] +DEFAULT_NAME = "FFmpeg" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_INPUT): cv.string, - vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_INPUT): cv.string, + vol.Optional(CONF_EXTRA_ARGUMENTS): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): 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 a FFmpeg camera.""" if not hass.data[DATA_FFMPEG].async_run_test(config.get(CONF_INPUT)): return @@ -52,11 +55,15 @@ class FFmpegCamera(Camera): async def async_camera_image(self): """Return a still image response from the camera.""" from haffmpeg import ImageFrame, IMAGE_JPEG + ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop) - image = await asyncio.shield(ffmpeg.get_image( - self._input, output_format=IMAGE_JPEG, - extra_cmd=self._extra_arguments), loop=self.hass.loop) + image = await asyncio.shield( + ffmpeg.get_image( + self._input, output_format=IMAGE_JPEG, extra_cmd=self._extra_arguments + ), + loop=self.hass.loop, + ) return image async def handle_async_mjpeg_stream(self, request): @@ -64,13 +71,15 @@ class FFmpegCamera(Camera): from haffmpeg import CameraMjpeg stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop) - await stream.open_camera( - self._input, extra_cmd=self._extra_arguments) + await stream.open_camera(self._input, extra_cmd=self._extra_arguments) try: return await async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self.hass, + request, + stream, + "multipart/x-mixed-replace;boundary=ffserver", + ) finally: await stream.close() diff --git a/homeassistant/components/camera/foscam.py b/homeassistant/components/camera/foscam.py index ceec57f77..213871a1e 100644 --- a/homeassistant/components/camera/foscam.py +++ b/homeassistant/components/camera/foscam.py @@ -8,29 +8,30 @@ import logging import voluptuous as vol -from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA) -from homeassistant.const import ( - CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT) +from homeassistant.components.camera import Camera, PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_PORT from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['libpyfoscam==1.0'] +REQUIREMENTS = ["libpyfoscam==1.0"] -CONF_IP = 'ip' +CONF_IP = "ip" -DEFAULT_NAME = 'Foscam Camera' +DEFAULT_NAME = "Foscam Camera" DEFAULT_PORT = 88 FOSCAM_COMM_ERROR = -8 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_IP): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_IP): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -55,7 +56,8 @@ class FoscamCam(Camera): self._motion_status = False self._foscam_session = FoscamCamera( - ip_address, port, self._username, self._password, verbose=False) + ip_address, port, self._username, self._password, verbose=False + ) def camera_image(self): """Return a still image response from the camera.""" diff --git a/homeassistant/components/camera/generic.py b/homeassistant/components/camera/generic.py index b707c9134..cf45ce4f6 100644 --- a/homeassistant/components/camera/generic.py +++ b/homeassistant/components/camera/generic.py @@ -14,41 +14,52 @@ from requests.auth import HTTPDigestAuth import voluptuous as vol from homeassistant.const import ( - CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_AUTHENTICATION, - HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION, CONF_VERIFY_SSL) + CONF_NAME, + CONF_USERNAME, + CONF_PASSWORD, + CONF_AUTHENTICATION, + HTTP_BASIC_AUTHENTICATION, + HTTP_DIGEST_AUTHENTICATION, + CONF_VERIFY_SSL, +) from homeassistant.exceptions import TemplateError from homeassistant.components.camera import ( - PLATFORM_SCHEMA, DEFAULT_CONTENT_TYPE, Camera) + PLATFORM_SCHEMA, + DEFAULT_CONTENT_TYPE, + Camera, +) from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers import config_validation as cv from homeassistant.util.async_ import run_coroutine_threadsafe _LOGGER = logging.getLogger(__name__) -CONF_CONTENT_TYPE = 'content_type' -CONF_LIMIT_REFETCH_TO_URL_CHANGE = 'limit_refetch_to_url_change' -CONF_STILL_IMAGE_URL = 'still_image_url' -CONF_FRAMERATE = 'framerate' +CONF_CONTENT_TYPE = "content_type" +CONF_LIMIT_REFETCH_TO_URL_CHANGE = "limit_refetch_to_url_change" +CONF_STILL_IMAGE_URL = "still_image_url" +CONF_FRAMERATE = "framerate" -DEFAULT_NAME = 'Generic Camera' +DEFAULT_NAME = "Generic Camera" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_STILL_IMAGE_URL): cv.template, - vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): - vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]), - vol.Optional(CONF_LIMIT_REFETCH_TO_URL_CHANGE, default=False): cv.boolean, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_USERNAME): cv.string, - vol.Optional(CONF_CONTENT_TYPE, default=DEFAULT_CONTENT_TYPE): cv.string, - vol.Optional(CONF_FRAMERATE, default=2): cv.positive_int, - vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_STILL_IMAGE_URL): cv.template, + vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): vol.In( + [HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION] + ), + vol.Optional(CONF_LIMIT_REFETCH_TO_URL_CHANGE, default=False): cv.boolean, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_USERNAME): cv.string, + vol.Optional(CONF_CONTENT_TYPE, default=DEFAULT_CONTENT_TYPE): cv.string, + vol.Optional(CONF_FRAMERATE, default=2): cv.positive_int, + vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, + } +) @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 a generic IP Camera.""" async_add_entities([GenericCamera(hass, config)]) @@ -91,7 +102,8 @@ class GenericCamera(Camera): def camera_image(self): """Return bytes of camera image.""" return run_coroutine_threadsafe( - self.async_camera_image(), self.hass.loop).result() + self.async_camera_image(), self.hass.loop + ).result() @asyncio.coroutine def async_camera_image(self): @@ -99,8 +111,7 @@ class GenericCamera(Camera): try: url = self._still_image_url.async_render() except TemplateError as err: - _LOGGER.error( - "Error parsing template %s: %s", self._still_image_url, err) + _LOGGER.error("Error parsing template %s: %s", self._still_image_url, err) return self._last_image if url == self._last_url and self._limit_refetch: @@ -108,26 +119,27 @@ class GenericCamera(Camera): # aiohttp don't support DigestAuth yet if self._authentication == HTTP_DIGEST_AUTHENTICATION: + def fetch(): """Read image from a URL.""" try: - response = requests.get(url, timeout=10, auth=self._auth, - verify=self.verify_ssl) + response = requests.get( + url, timeout=10, auth=self._auth, verify=self.verify_ssl + ) return response.content except requests.exceptions.RequestException as error: _LOGGER.error("Error getting camera image: %s", error) return self._last_image - self._last_image = yield from self.hass.async_add_job( - fetch) + self._last_image = yield from self.hass.async_add_job(fetch) # async else: try: websession = async_get_clientsession( - self.hass, verify_ssl=self.verify_ssl) + self.hass, verify_ssl=self.verify_ssl + ) with async_timeout.timeout(10, loop=self.hass.loop): - response = yield from websession.get( - url, auth=self._auth) + response = yield from websession.get(url, auth=self._auth) self._last_image = yield from response.read() except asyncio.TimeoutError: _LOGGER.error("Timeout getting camera image") diff --git a/homeassistant/components/camera/local_file.py b/homeassistant/components/camera/local_file.py index d306509b7..24018969c 100644 --- a/homeassistant/components/camera/local_file.py +++ b/homeassistant/components/camera/local_file.py @@ -12,23 +12,29 @@ import voluptuous as vol from homeassistant.const import CONF_NAME from homeassistant.components.camera import ( - Camera, CAMERA_SERVICE_SCHEMA, DOMAIN, PLATFORM_SCHEMA) + Camera, + CAMERA_SERVICE_SCHEMA, + DOMAIN, + PLATFORM_SCHEMA, +) from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_FILE_PATH = 'file_path' -DEFAULT_NAME = 'Local File' -SERVICE_UPDATE_FILE_PATH = 'local_file_update_file_path' +CONF_FILE_PATH = "file_path" +DEFAULT_NAME = "Local File" +SERVICE_UPDATE_FILE_PATH = "local_file_update_file_path" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_FILE_PATH): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_FILE_PATH): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) -CAMERA_SERVICE_UPDATE_FILE_PATH = CAMERA_SERVICE_SCHEMA.extend({ - vol.Required(CONF_FILE_PATH): cv.string -}) +CAMERA_SERVICE_UPDATE_FILE_PATH = CAMERA_SERVICE_SCHEMA.extend( + {vol.Required(CONF_FILE_PATH): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -46,7 +52,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): DOMAIN, SERVICE_UPDATE_FILE_PATH, update_file_path_service, - schema=CAMERA_SERVICE_UPDATE_FILE_PATH) + schema=CAMERA_SERVICE_UPDATE_FILE_PATH, + ) add_entities([camera]) @@ -69,17 +76,21 @@ class LocalFile(Camera): def camera_image(self): """Return image response.""" try: - with open(self._file_path, 'rb') as file: + with open(self._file_path, "rb") as file: return file.read() except FileNotFoundError: - _LOGGER.warning("Could not read camera %s image from file: %s", - self._name, self._file_path) + _LOGGER.warning( + "Could not read camera %s image from file: %s", + self._name, + self._file_path, + ) def check_file_path_access(self, file_path): """Check that filepath given is readable.""" if not os.access(file_path, os.R_OK): - _LOGGER.warning("Could not read camera %s image from file: %s", - self._name, file_path) + _LOGGER.warning( + "Could not read camera %s image from file: %s", self._name, file_path + ) def update_file_path(self, file_path): """Update the file_path.""" @@ -95,6 +106,4 @@ class LocalFile(Camera): @property def device_state_attributes(self): """Return the camera state attributes.""" - return { - 'file_path': self._file_path, - } + return {"file_path": self._file_path} diff --git a/homeassistant/components/camera/mjpeg.py b/homeassistant/components/camera/mjpeg.py index f1917aaf2..a67d338db 100644 --- a/homeassistant/components/camera/mjpeg.py +++ b/homeassistant/components/camera/mjpeg.py @@ -15,35 +15,44 @@ from requests.auth import HTTPBasicAuth, HTTPDigestAuth import voluptuous as vol from homeassistant.const import ( - CONF_NAME, CONF_USERNAME, CONF_PASSWORD, CONF_AUTHENTICATION, - HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION) -from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera) + CONF_NAME, + CONF_USERNAME, + CONF_PASSWORD, + CONF_AUTHENTICATION, + HTTP_BASIC_AUTHENTICATION, + HTTP_DIGEST_AUTHENTICATION, +) +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera from homeassistant.helpers.aiohttp_client import ( - async_get_clientsession, async_aiohttp_proxy_web) + async_get_clientsession, + async_aiohttp_proxy_web, +) from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_MJPEG_URL = 'mjpeg_url' -CONF_STILL_IMAGE_URL = 'still_image_url' -CONTENT_TYPE_HEADER = 'Content-Type' +CONF_MJPEG_URL = "mjpeg_url" +CONF_STILL_IMAGE_URL = "still_image_url" +CONTENT_TYPE_HEADER = "Content-Type" -DEFAULT_NAME = 'Mjpeg Camera' +DEFAULT_NAME = "Mjpeg Camera" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_MJPEG_URL): cv.url, - vol.Optional(CONF_STILL_IMAGE_URL): cv.url, - vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): - vol.In([HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION]), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_USERNAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_MJPEG_URL): cv.url, + vol.Optional(CONF_STILL_IMAGE_URL): cv.url, + vol.Optional(CONF_AUTHENTICATION, default=HTTP_BASIC_AUTHENTICATION): vol.In( + [HTTP_BASIC_AUTHENTICATION, HTTP_DIGEST_AUTHENTICATION] + ), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_USERNAME): cv.string, + } +) @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 a MJPEG IP Camera.""" if discovery_info: config = PLATFORM_SCHEMA(discovery_info) @@ -52,13 +61,13 @@ def async_setup_platform(hass, config, async_add_entities, def extract_image_from_mjpeg(stream): """Take in a MJPEG stream object, return the jpg from it.""" - data = b'' + data = b"" for chunk in stream: data += chunk - jpg_start = data.find(b'\xff\xd8') - jpg_end = data.find(b'\xff\xd9') + jpg_start = data.find(b"\xff\xd8") + jpg_end = data.find(b"\xff\xd9") if jpg_start != -1 and jpg_end != -1: - jpg = data[jpg_start:jpg_end + 2] + jpg = data[jpg_start : jpg_end + 2] return jpg @@ -78,25 +87,25 @@ class MjpegCamera(Camera): self._auth = None if self._username and self._password: if self._authentication == HTTP_BASIC_AUTHENTICATION: - self._auth = aiohttp.BasicAuth( - self._username, password=self._password - ) + self._auth = aiohttp.BasicAuth(self._username, password=self._password) @asyncio.coroutine def async_camera_image(self): """Return a still image response from the camera.""" # DigestAuth is not supported - if self._authentication == HTTP_DIGEST_AUTHENTICATION or \ - self._still_image_url is None: - image = yield from self.hass.async_add_job( - self.camera_image) + if ( + self._authentication == HTTP_DIGEST_AUTHENTICATION + or self._still_image_url is None + ): + image = yield from self.hass.async_add_job(self.camera_image) return image websession = async_get_clientsession(self.hass) try: with async_timeout.timeout(10, loop=self.hass.loop): response = yield from websession.get( - self._still_image_url, auth=self._auth) + self._still_image_url, auth=self._auth + ) image = yield from response.read() return image @@ -114,8 +123,7 @@ class MjpegCamera(Camera): auth = HTTPDigestAuth(self._username, self._password) else: auth = HTTPBasicAuth(self._username, self._password) - req = requests.get( - self._mjpeg_url, auth=auth, stream=True, timeout=10) + req = requests.get(self._mjpeg_url, auth=auth, stream=True, timeout=10) else: req = requests.get(self._mjpeg_url, stream=True, timeout=10) diff --git a/homeassistant/components/camera/mqtt.py b/homeassistant/components/camera/mqtt.py index cf5c969c6..b799d1708 100644 --- a/homeassistant/components/camera/mqtt.py +++ b/homeassistant/components/camera/mqtt.py @@ -18,28 +18,26 @@ from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_TOPIC = 'topic' -DEFAULT_NAME = 'MQTT Camera' +CONF_TOPIC = "topic" +DEFAULT_NAME = "MQTT Camera" -DEPENDENCIES = ['mqtt'] +DEPENDENCIES = ["mqtt"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) @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 MQTT Camera.""" if discovery_info is not None: config = PLATFORM_SCHEMA(discovery_info) - async_add_entities([MqttCamera( - config.get(CONF_NAME), - config.get(CONF_TOPIC) - )]) + async_add_entities([MqttCamera(config.get(CONF_NAME), config.get(CONF_TOPIC))]) class MqttCamera(Camera): @@ -67,10 +65,12 @@ class MqttCamera(Camera): @asyncio.coroutine def async_added_to_hass(self): """Subscribe MQTT events.""" + @callback def message_received(topic, payload, qos): """Handle new MQTT messages.""" self._last_image = payload return mqtt.async_subscribe( - self.hass, self._topic, message_received, self._qos, None) + self.hass, self._topic, message_received, self._qos, None + ) diff --git a/homeassistant/components/camera/neato.py b/homeassistant/components/camera/neato.py index b080dbbae..5be6079c9 100644 --- a/homeassistant/components/camera/neato.py +++ b/homeassistant/components/camera/neato.py @@ -8,12 +8,11 @@ import logging from datetime import timedelta from homeassistant.components.camera import Camera -from homeassistant.components.neato import ( - NEATO_MAP_DATA, NEATO_ROBOTS, NEATO_LOGIN) +from homeassistant.components.neato import NEATO_MAP_DATA, NEATO_ROBOTS, NEATO_LOGIN _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['neato'] +DEPENDENCIES = ["neato"] SCAN_INTERVAL = timedelta(minutes=10) @@ -22,7 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Neato Camera.""" dev = [] for robot in hass.data[NEATO_ROBOTS]: - if 'maps' in robot.traits: + if "maps" in robot.traits: dev.append(NeatoCleaningMap(hass, robot)) _LOGGER.debug("Adding robots for cleaning maps %s", dev) add_entities(dev, True) @@ -35,7 +34,7 @@ class NeatoCleaningMap(Camera): """Initialize Neato cleaning map.""" super().__init__() self.robot = robot - self._robot_name = '{} {}'.format(self.robot.name, 'Cleaning Map') + self._robot_name = "{} {}".format(self.robot.name, "Cleaning Map") self._robot_serial = self.robot.serial self.neato = hass.data[NEATO_LOGIN] self._image_url = None @@ -51,7 +50,7 @@ class NeatoCleaningMap(Camera): self.neato.update_robots() image_url = None map_data = self.hass.data[NEATO_MAP_DATA] - image_url = map_data[self._robot_serial]['maps'][0]['url'] + image_url = map_data[self._robot_serial]["maps"][0]["url"] if image_url == self._image_url: _LOGGER.debug("The map image_url is the same as old") return diff --git a/homeassistant/components/camera/nest.py b/homeassistant/components/camera/nest.py index e1d263719..f4de113f7 100644 --- a/homeassistant/components/camera/nest.py +++ b/homeassistant/components/camera/nest.py @@ -10,15 +10,14 @@ from datetime import timedelta import requests from homeassistant.components import nest -from homeassistant.components.camera import (PLATFORM_SCHEMA, Camera, - SUPPORT_ON_OFF) +from homeassistant.components.camera import PLATFORM_SCHEMA, Camera, SUPPORT_ON_OFF from homeassistant.util.dt import utcnow _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['nest'] +DEPENDENCIES = ["nest"] -NEST_BRAND = 'Nest' +NEST_BRAND = "Nest" PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({}) @@ -32,10 +31,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): async def async_setup_entry(hass, entry, async_add_entities): """Set up a Nest sensor based on a config entry.""" - camera_devices = \ - await hass.async_add_job(hass.data[nest.DATA_NEST].cameras) - cameras = [NestCamera(structure, device) - for structure, device in camera_devices] + camera_devices = await hass.async_add_job(hass.data[nest.DATA_NEST].cameras) + cameras = [NestCamera(structure, device) for structure, device in camera_devices] async_add_entities(cameras, True) @@ -89,7 +86,7 @@ class NestCamera(Camera): def turn_off(self): """Turn off camera.""" - _LOGGER.debug('Turn off camera %s', self._name) + _LOGGER.debug("Turn off camera %s", self._name) # Calling Nest API in is_streaming setter. # device.is_streaming would not immediately change until the process # finished in Nest Cam. @@ -98,10 +95,10 @@ class NestCamera(Camera): def turn_on(self): """Turn on camera.""" if not self._online: - _LOGGER.error('Camera %s is offline.', self._name) + _LOGGER.error("Camera %s is offline.", self._name) return - _LOGGER.debug('Turn on camera %s', self._name) + _LOGGER.debug("Turn on camera %s", self._name) # Calling Nest API in is_streaming setter. # device.is_streaming would not immediately change until the process # finished in Nest Cam. @@ -123,8 +120,7 @@ class NestCamera(Camera): self._time_between_snapshots = timedelta(seconds=30) def _ready_for_snapshot(self, now): - return (self._next_snapshot_at is None or - now > self._next_snapshot_at) + return self._next_snapshot_at is None or now > self._next_snapshot_at def camera_image(self): """Return a still image response from the camera.""" diff --git a/homeassistant/components/camera/netatmo.py b/homeassistant/components/camera/netatmo.py index 93ad2cd05..b7f28e6ba 100644 --- a/homeassistant/components/camera/netatmo.py +++ b/homeassistant/components/camera/netatmo.py @@ -11,22 +11,23 @@ import voluptuous as vol from homeassistant.const import CONF_VERIFY_SSL from homeassistant.components.netatmo import CameraData -from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA) +from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.helpers import config_validation as cv -DEPENDENCIES = ['netatmo'] +DEPENDENCIES = ["netatmo"] _LOGGER = logging.getLogger(__name__) -CONF_HOME = 'home' -CONF_CAMERAS = 'cameras' +CONF_HOME = "home" +CONF_CAMERAS = "cameras" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, - vol.Optional(CONF_HOME): cv.string, - vol.Optional(CONF_CAMERAS, default=[]): - vol.All(cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, + vol.Optional(CONF_HOME): cv.string, + vol.Optional(CONF_CAMERAS, default=[]): vol.All(cv.ensure_list, [cv.string]), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -35,16 +36,20 @@ def setup_platform(hass, config, add_entities, discovery_info=None): home = config.get(CONF_HOME) verify_ssl = config.get(CONF_VERIFY_SSL, True) import pyatmo + try: data = CameraData(netatmo.NETATMO_AUTH, home) for camera_name in data.get_camera_names(): camera_type = data.get_camera_type(camera=camera_name, home=home) if CONF_CAMERAS in config: - if config[CONF_CAMERAS] != [] and \ - camera_name not in config[CONF_CAMERAS]: + if ( + config[CONF_CAMERAS] != [] + and camera_name not in config[CONF_CAMERAS] + ): continue - add_entities([NetatmoCamera(data, camera_name, home, - camera_type, verify_ssl)]) + add_entities( + [NetatmoCamera(data, camera_name, home, camera_type, verify_ssl)] + ) except pyatmo.NoDevice: return None @@ -59,34 +64,40 @@ class NetatmoCamera(Camera): self._camera_name = camera_name self._verify_ssl = verify_ssl if home: - self._name = home + ' / ' + camera_name + self._name = home + " / " + camera_name else: self._name = camera_name self._vpnurl, self._localurl = self._data.camera_data.cameraUrls( camera=camera_name - ) + ) self._cameratype = camera_type def camera_image(self): """Return a still image response from the camera.""" try: if self._localurl: - response = requests.get('{0}/live/snapshot_720.jpg'.format( - self._localurl), timeout=10) + response = requests.get( + "{0}/live/snapshot_720.jpg".format(self._localurl), timeout=10 + ) elif self._vpnurl: - response = requests.get('{0}/live/snapshot_720.jpg'.format( - self._vpnurl), timeout=10, verify=self._verify_ssl) + response = requests.get( + "{0}/live/snapshot_720.jpg".format(self._vpnurl), + timeout=10, + verify=self._verify_ssl, + ) else: _LOGGER.error("Welcome VPN URL is None") self._data.update() - (self._vpnurl, self._localurl) = \ - self._data.camera_data.cameraUrls(camera=self._camera_name) + (self._vpnurl, self._localurl) = self._data.camera_data.cameraUrls( + camera=self._camera_name + ) return None except requests.exceptions.RequestException as error: _LOGGER.error("Welcome URL changed: %s", error) self._data.update() - (self._vpnurl, self._localurl) = \ - self._data.camera_data.cameraUrls(camera=self._camera_name) + (self._vpnurl, self._localurl) = self._data.camera_data.cameraUrls( + camera=self._camera_name + ) return None return response.content diff --git a/homeassistant/components/camera/onvif.py b/homeassistant/components/camera/onvif.py index 9cf21dca9..c15579764 100644 --- a/homeassistant/components/camera/onvif.py +++ b/homeassistant/components/camera/onvif.py @@ -11,27 +11,32 @@ import os import voluptuous as vol from homeassistant.const import ( - CONF_NAME, CONF_HOST, CONF_USERNAME, CONF_PASSWORD, CONF_PORT, - ATTR_ENTITY_ID) + CONF_NAME, + CONF_HOST, + CONF_USERNAME, + CONF_PASSWORD, + CONF_PORT, + ATTR_ENTITY_ID, +) from homeassistant.components.camera import Camera, PLATFORM_SCHEMA, DOMAIN -from homeassistant.components.ffmpeg import ( - DATA_FFMPEG, CONF_EXTRA_ARGUMENTS) +from homeassistant.components.ffmpeg import DATA_FFMPEG, CONF_EXTRA_ARGUMENTS import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.aiohttp_client import ( - async_aiohttp_proxy_stream) +from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream from homeassistant.helpers.service import extract_entity_ids _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['onvif-py3==0.1.3', - 'suds-py3==1.3.3.0', - 'suds-passworddigest-homeassistant==0.1.2a0.dev0'] -DEPENDENCIES = ['ffmpeg'] -DEFAULT_NAME = 'ONVIF Camera' +REQUIREMENTS = [ + "onvif-py3==0.1.3", + "suds-py3==1.3.3.0", + "suds-passworddigest-homeassistant==0.1.2a0.dev0", +] +DEPENDENCIES = ["ffmpeg"] +DEFAULT_NAME = "ONVIF Camera" DEFAULT_PORT = 5000 -DEFAULT_USERNAME = 'admin' -DEFAULT_PASSWORD = '888888' -DEFAULT_ARGUMENTS = '-q:v 2' +DEFAULT_USERNAME = "admin" +DEFAULT_PASSWORD = "888888" +DEFAULT_ARGUMENTS = "-q:v 2" DEFAULT_PROFILE = 0 CONF_PROFILE = "profile" @@ -52,23 +57,28 @@ SERVICE_PTZ = "onvif_ptz" ONVIF_DATA = "onvif" ENTITIES = "entities" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, - vol.Optional(CONF_PROFILE, default=DEFAULT_PROFILE): - vol.All(vol.Coerce(int), vol.Range(min=0)), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_EXTRA_ARGUMENTS, default=DEFAULT_ARGUMENTS): cv.string, + vol.Optional(CONF_PROFILE, default=DEFAULT_PROFILE): vol.All( + vol.Coerce(int), vol.Range(min=0) + ), + } +) -SERVICE_PTZ_SCHEMA = vol.Schema({ - ATTR_ENTITY_ID: cv.entity_ids, - ATTR_PAN: vol.In([DIR_LEFT, DIR_RIGHT]), - ATTR_TILT: vol.In([DIR_UP, DIR_DOWN]), - ATTR_ZOOM: vol.In([ZOOM_OUT, ZOOM_IN]) -}) +SERVICE_PTZ_SCHEMA = vol.Schema( + { + ATTR_ENTITY_ID: cv.entity_ids, + ATTR_PAN: vol.In([DIR_LEFT, DIR_RIGHT]), + ATTR_TILT: vol.In([DIR_UP, DIR_DOWN]), + ATTR_ZOOM: vol.In([ZOOM_OUT, ZOOM_IN]), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -87,13 +97,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if not entity_ids: target_cameras = all_cameras else: - target_cameras = [camera for camera in all_cameras - if camera.entity_id in entity_ids] + target_cameras = [ + camera for camera in all_cameras if camera.entity_id in entity_ids + ] for camera in target_cameras: camera.perform_ptz(pan, tilt, zoom) - hass.services.async_register(DOMAIN, SERVICE_PTZ, handle_ptz, - schema=SERVICE_PTZ_SCHEMA) + hass.services.async_register( + DOMAIN, SERVICE_PTZ, handle_ptz, schema=SERVICE_PTZ_SCHEMA + ) add_entities([ONVIFHassCamera(hass, config)]) @@ -113,72 +125,80 @@ class ONVIFHassCamera(Camera): self._ffmpeg_arguments = config.get(CONF_EXTRA_ARGUMENTS) self._profile_index = config.get(CONF_PROFILE) self._input = None - self._media_service = \ - onvif.ONVIFService('http://{}:{}/onvif/device_service'.format( - self._host, self._port), - self._username, self._password, - '{}/wsdl/media.wsdl'.format(os.path.dirname( - onvif.__file__))) + self._media_service = onvif.ONVIFService( + "http://{}:{}/onvif/device_service".format(self._host, self._port), + self._username, + self._password, + "{}/wsdl/media.wsdl".format(os.path.dirname(onvif.__file__)), + ) - self._ptz_service = \ - onvif.ONVIFService('http://{}:{}/onvif/device_service'.format( - self._host, self._port), - self._username, self._password, - '{}/wsdl/ptz.wsdl'.format(os.path.dirname( - onvif.__file__))) + self._ptz_service = onvif.ONVIFService( + "http://{}:{}/onvif/device_service".format(self._host, self._port), + self._username, + self._password, + "{}/wsdl/ptz.wsdl".format(os.path.dirname(onvif.__file__)), + ) def obtain_input_uri(self): """Set the input uri for the camera.""" from onvif import exceptions - _LOGGER.debug("Connecting with ONVIF Camera: %s on port %s", - self._host, self._port) + + _LOGGER.debug( + "Connecting with ONVIF Camera: %s on port %s", self._host, self._port + ) try: profiles = self._media_service.GetProfiles() if self._profile_index >= len(profiles): - _LOGGER.warning("ONVIF Camera '%s' doesn't provide profile %d." - " Using the last profile.", - self._name, self._profile_index) + _LOGGER.warning( + "ONVIF Camera '%s' doesn't provide profile %d." + " Using the last profile.", + self._name, + self._profile_index, + ) self._profile_index = -1 - req = self._media_service.create_type('GetStreamUri') + req = self._media_service.create_type("GetStreamUri") # pylint: disable=protected-access req.ProfileToken = profiles[self._profile_index]._token uri_no_auth = self._media_service.GetStreamUri(req).Uri - uri_for_log = uri_no_auth.replace( - 'rtsp://', 'rtsp://:@', 1) + uri_for_log = uri_no_auth.replace("rtsp://", "rtsp://:@", 1) self._input = uri_no_auth.replace( - 'rtsp://', 'rtsp://{}:{}@'.format(self._username, - self._password), 1) + "rtsp://", "rtsp://{}:{}@".format(self._username, self._password), 1 + ) _LOGGER.debug( "ONVIF Camera Using the following URL for %s: %s", - self._name, uri_for_log) + self._name, + uri_for_log, + ) # we won't need the media service anymore self._media_service = None except exceptions.ONVIFError as err: - _LOGGER.debug("Couldn't setup camera '%s'. Error: %s", - self._name, err) + _LOGGER.debug("Couldn't setup camera '%s'. Error: %s", self._name, err) return def perform_ptz(self, pan, tilt, zoom): """Perform a PTZ action on the camera.""" from onvif import exceptions + if self._ptz_service: pan_val = 1 if pan == DIR_RIGHT else -1 if pan == DIR_LEFT else 0 tilt_val = 1 if tilt == DIR_UP else -1 if tilt == DIR_DOWN else 0 zoom_val = 1 if zoom == ZOOM_IN else -1 if zoom == ZOOM_OUT else 0 - req = {"Velocity": { - "PanTilt": {"_x": pan_val, "_y": tilt_val}, - "Zoom": {"_x": zoom_val}}} + req = { + "Velocity": { + "PanTilt": {"_x": pan_val, "_y": tilt_val}, + "Zoom": {"_x": zoom_val}, + } + } try: self._ptz_service.ContinuousMove(req) except exceptions.ONVIFError as err: if "Bad Request" in err.reason: self._ptz_service = None - _LOGGER.debug("Camera '%s' doesn't support PTZ.", - self._name) + _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) else: _LOGGER.debug("Camera '%s' doesn't support PTZ.", self._name) @@ -198,12 +218,14 @@ class ONVIFHassCamera(Camera): if not self._input: return None - ffmpeg = ImageFrame( - self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop) + ffmpeg = ImageFrame(self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop) - image = await asyncio.shield(ffmpeg.get_image( - self._input, output_format=IMAGE_JPEG, - extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop) + image = await asyncio.shield( + ffmpeg.get_image( + self._input, output_format=IMAGE_JPEG, extra_cmd=self._ffmpeg_arguments + ), + loop=self.hass.loop, + ) return image async def handle_async_mjpeg_stream(self, request): @@ -215,14 +237,12 @@ class ONVIFHassCamera(Camera): if not self._input: return None - stream = CameraMjpeg(self.hass.data[DATA_FFMPEG].binary, - loop=self.hass.loop) - await stream.open_camera( - self._input, extra_cmd=self._ffmpeg_arguments) + stream = CameraMjpeg(self.hass.data[DATA_FFMPEG].binary, loop=self.hass.loop) + await stream.open_camera(self._input, extra_cmd=self._ffmpeg_arguments) await async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self.hass, request, stream, "multipart/x-mixed-replace;boundary=ffserver" + ) await stream.close() @property diff --git a/homeassistant/components/camera/proxy.py b/homeassistant/components/camera/proxy.py index 83d873116..5f06d5969 100644 --- a/homeassistant/components/camera/proxy.py +++ b/homeassistant/components/camera/proxy.py @@ -17,36 +17,37 @@ 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'] +REQUIREMENTS = ["pillow==5.2.0"] _LOGGER = logging.getLogger(__name__) -CONF_CACHE_IMAGES = 'cache_images' -CONF_FORCE_RESIZE = 'force_resize' -CONF_IMAGE_QUALITY = 'image_quality' -CONF_IMAGE_REFRESH_RATE = 'image_refresh_rate' -CONF_MAX_IMAGE_WIDTH = 'max_image_width' -CONF_MAX_STREAM_WIDTH = 'max_stream_width' -CONF_STREAM_QUALITY = 'stream_quality' +CONF_CACHE_IMAGES = "cache_images" +CONF_FORCE_RESIZE = "force_resize" +CONF_IMAGE_QUALITY = "image_quality" +CONF_IMAGE_REFRESH_RATE = "image_refresh_rate" +CONF_MAX_IMAGE_WIDTH = "max_image_width" +CONF_MAX_STREAM_WIDTH = "max_stream_width" +CONF_STREAM_QUALITY = "stream_quality" DEFAULT_BASENAME = "Camera Proxy" DEFAULT_QUALITY = 75 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_ENTITY_ID): cv.entity_id, - vol.Optional(CONF_CACHE_IMAGES, False): cv.boolean, - vol.Optional(CONF_FORCE_RESIZE, False): cv.boolean, - vol.Optional(CONF_IMAGE_QUALITY): int, - vol.Optional(CONF_IMAGE_REFRESH_RATE): float, - vol.Optional(CONF_MAX_IMAGE_WIDTH): int, - vol.Optional(CONF_MAX_STREAM_WIDTH): int, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_STREAM_QUALITY): int, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_ENTITY_ID): cv.entity_id, + vol.Optional(CONF_CACHE_IMAGES, False): cv.boolean, + vol.Optional(CONF_FORCE_RESIZE, False): cv.boolean, + vol.Optional(CONF_IMAGE_QUALITY): int, + vol.Optional(CONF_IMAGE_REFRESH_RATE): float, + vol.Optional(CONF_MAX_IMAGE_WIDTH): int, + vol.Optional(CONF_MAX_STREAM_WIDTH): int, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_STREAM_QUALITY): int, + } +) -async def async_setup_platform( - hass, config, async_add_entities, discovery_info=None): +async def async_setup_platform(hass, config, async_add_entities, discovery_info=None): """Set up the Proxy camera platform.""" async_add_entities([ProxyCamera(hass, config)]) @@ -67,7 +68,7 @@ def _resize_image(image, opts): except IOError: return image imgfmt = str(img.format) - if imgfmt not in ('PNG', 'JPEG'): + if imgfmt not in ("PNG", "JPEG"): _LOGGER.debug("Image is of unsupported type: %s", imgfmt) return image @@ -80,25 +81,34 @@ def _resize_image(image, opts): new_width = old_width scale = new_width / float(old_width) - new_height = int((float(old_height)*float(scale))) + new_height = int((float(old_height) * float(scale))) img = img.resize((new_width, new_height), Image.ANTIALIAS) imgbuf = io.BytesIO() - img.save(imgbuf, 'JPEG', optimize=True, quality=quality) + img.save(imgbuf, "JPEG", optimize=True, quality=quality) newimage = imgbuf.getvalue() if not opts.force_resize and len(newimage) >= old_size: - _LOGGER.debug("Using original image(%d bytes) " - "because resized image (%d bytes) is not smaller", - old_size, len(newimage)) + _LOGGER.debug( + "Using original image(%d bytes) " + "because resized image (%d bytes) is not smaller", + old_size, + len(newimage), + ) return image _LOGGER.debug( "Resized image from (%dx%d - %d bytes) to (%dx%d - %d bytes)", - old_width, old_height, old_size, new_width, new_height, len(newimage)) + old_width, + old_height, + old_size, + new_width, + new_height, + len(newimage), + ) return newimage -class ImageOpts(): +class ImageOpts: """The representation of image options.""" def __init__(self, max_width, quality, force_resize): @@ -120,50 +130,56 @@ class ProxyCamera(Camera): super().__init__() self.hass = hass self._proxied_camera = config.get(CONF_ENTITY_ID) - self._name = ( - config.get(CONF_NAME) or - "{} - {}".format(DEFAULT_BASENAME, self._proxied_camera)) + self._name = config.get(CONF_NAME) or "{} - {}".format( + DEFAULT_BASENAME, self._proxied_camera + ) self._image_opts = ImageOpts( config.get(CONF_MAX_IMAGE_WIDTH), config.get(CONF_IMAGE_QUALITY), - config.get(CONF_FORCE_RESIZE)) + config.get(CONF_FORCE_RESIZE), + ) self._stream_opts = ImageOpts( - config.get(CONF_MAX_STREAM_WIDTH), config.get(CONF_STREAM_QUALITY), - True) + config.get(CONF_MAX_STREAM_WIDTH), config.get(CONF_STREAM_QUALITY), True + ) self._image_refresh_rate = config.get(CONF_IMAGE_REFRESH_RATE) self._cache_images = bool( - config.get(CONF_IMAGE_REFRESH_RATE) - or config.get(CONF_CACHE_IMAGES)) + config.get(CONF_IMAGE_REFRESH_RATE) or config.get(CONF_CACHE_IMAGES) + ) self._last_image_time = 0 self._last_image = None self._headers = ( {HTTP_HEADER_HA_AUTH: self.hass.config.api.api_password} - if self.hass.config.api.api_password is not None else None) + if self.hass.config.api.api_password is not None + else None + ) def camera_image(self): """Return camera image.""" return run_coroutine_threadsafe( - self.async_camera_image(), self.hass.loop).result() + self.async_camera_image(), self.hass.loop + ).result() async def async_camera_image(self): """Return a still image response from the camera.""" now = dt_util.utcnow() - if (self._image_refresh_rate and - now < self._last_image_time + self._image_refresh_rate): + if ( + self._image_refresh_rate + and now < self._last_image_time + self._image_refresh_rate + ): return self._last_image self._last_image_time = now - image = await self.hass.components.camera.async_get_image( - self._proxied_camera) + 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.content, self._image_opts) + _resize_image, image.content, self._image_opts + ) if self._cache_images: self._last_image = image @@ -173,11 +189,12 @@ class ProxyCamera(Camera): """Generate an HTTP MJPEG stream from camera images.""" if not self._stream_opts: return await self.hass.components.camera.async_get_mjpeg_stream( - request, self._proxied_camera) + request, self._proxied_camera + ) return await async_get_still_stream( - request, self._async_stream_image, - self.content_type, self.frame_interval) + request, self._async_stream_image, self.content_type, self.frame_interval + ) @property def name(self): @@ -188,11 +205,13 @@ class ProxyCamera(Camera): """Return a still image response from the camera.""" try: image = await self.hass.components.camera.async_get_image( - self._proxied_camera) + 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) + _resize_image, image.content, self._stream_opts + ) diff --git a/homeassistant/components/camera/push.py b/homeassistant/components/camera/push.py index c9deca130..de5fcfb39 100644 --- a/homeassistant/components/camera/push.py +++ b/homeassistant/components/camera/push.py @@ -10,56 +10,69 @@ from collections import deque from datetime import timedelta import voluptuous as vol -from homeassistant.components.camera import Camera, PLATFORM_SCHEMA,\ - STATE_IDLE, STATE_RECORDING +from homeassistant.components.camera import ( + Camera, + PLATFORM_SCHEMA, + STATE_IDLE, + STATE_RECORDING, +) from homeassistant.core import callback -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.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 _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['http'] +DEPENDENCIES = ["http"] -CONF_BUFFER_SIZE = 'buffer' -CONF_IMAGE_FIELD = 'field' -CONF_TOKEN = 'token' +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' +ATTR_FILENAME = "filename" +ATTR_LAST_TRIP = "last_trip" +ATTR_TOKEN = "token" -PUSH_CAMERA_DATA = 'push_camera' +PUSH_CAMERA_DATA = "push_camera" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_BUFFER_SIZE, default=1): cv.positive_int, - 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)), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_BUFFER_SIZE, default=1): cv.positive_int, + 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)), + } +) -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 Push Camera platform.""" if PUSH_CAMERA_DATA not in hass.data: hass.data[PUSH_CAMERA_DATA] = {} - cameras = [PushCamera(config[CONF_NAME], - config[CONF_BUFFER_SIZE], - config[CONF_TIMEOUT], - config.get(CONF_TOKEN))] + cameras = [ + PushCamera( + config[CONF_NAME], + config[CONF_BUFFER_SIZE], + config[CONF_TIMEOUT], + config.get(CONF_TOKEN), + ) + ] - hass.http.register_view(CameraPushReceiver(hass, - config[CONF_IMAGE_FIELD])) + hass.http.register_view(CameraPushReceiver(hass, config[CONF_IMAGE_FIELD])) async_add_entities(cameras) @@ -68,7 +81,7 @@ class CameraPushReceiver(HomeAssistantView): """Handle pushes from remote camera.""" url = "/api/camera_push/{entity_id}" - name = 'api:camera_push:camera_entity' + name = "api:camera_push:camera_entity" requires_auth = False def __init__(self, hass, image_field): @@ -82,34 +95,33 @@ 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), - status) + status = HTTP_NOT_FOUND if request[KEY_AUTHENTICATED] else HTTP_UNAUTHORIZED + return self.json_message("Unknown {}".format(entity_id), 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)) + 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) + "Invalid authorization credentials for {}".format(entity_id), + HTTP_UNAUTHORIZED, + ) try: data = await request.post() _LOGGER.debug("Received Camera push: %s", data[self._image]) - await _camera.update_image(data[self._image].file.read(), - data[self._image].filename) + await _camera.update_image( + data[self._image].file.read(), data[self._image].filename + ) except ValueError as value_error: _LOGGER.error("Unknown value %s", value_error) - return self.json_message('Invalid POST', HTTP_BAD_REQUEST) + return self.json_message("Invalid POST", HTTP_BAD_REQUEST) except KeyError as key_error: - _LOGGER.error('In your POST message %s', key_error) - return self.json_message('{} missing'.format(self._image), - HTTP_BAD_REQUEST) + _LOGGER.error("In your POST message %s", key_error) + return self.json_message("{} missing".format(self._image), HTTP_BAD_REQUEST) class PushCamera(Camera): @@ -159,7 +171,8 @@ class PushCamera(Camera): self._expired_listener() self._expired_listener = async_track_point_in_utc_time( - self.hass, reset_state, dt_util.utcnow() + self._timeout) + self.hass, reset_state, dt_util.utcnow() + self._timeout + ) self.async_schedule_update_ha_state() @@ -186,9 +199,11 @@ class PushCamera(Camera): def device_state_attributes(self): """Return the state attributes.""" return { - name: value for name, value in ( + 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 + ) + if value is not None } diff --git a/homeassistant/components/camera/ring.py b/homeassistant/components/camera/ring.py index f629b5018..5bc58eb98 100644 --- a/homeassistant/components/camera/ring.py +++ b/homeassistant/components/camera/ring.py @@ -12,36 +12,35 @@ from datetime import timedelta import voluptuous as vol from homeassistant.helpers import config_validation as cv -from homeassistant.components.ring import ( - DATA_RING, CONF_ATTRIBUTION, NOTIFICATION_ID) +from homeassistant.components.ring import DATA_RING, CONF_ATTRIBUTION, NOTIFICATION_ID from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.components.ffmpeg import DATA_FFMPEG from homeassistant.const import ATTR_ATTRIBUTION, CONF_SCAN_INTERVAL from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream from homeassistant.util import dt as dt_util -CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' +CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments" -DEPENDENCIES = ['ring', 'ffmpeg'] +DEPENDENCIES = ["ring", "ffmpeg"] FORCE_REFRESH_INTERVAL = timedelta(minutes=45) _LOGGER = logging.getLogger(__name__) -NOTIFICATION_TITLE = 'Ring Camera Setup' +NOTIFICATION_TITLE = "Ring Camera Setup" SCAN_INTERVAL = timedelta(seconds=90) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string, - vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): - cv.time_period, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string, + vol.Optional(CONF_SCAN_INTERVAL, default=SCAN_INTERVAL): cv.time_period, + } +) @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 a Ring Door Bell and StickUp Camera.""" ring = hass.data[DATA_RING] @@ -61,18 +60,21 @@ def async_setup_platform(hass, config, async_add_entities, # show notification for all cameras without an active subscription if cams_no_plan: - cameras = str(', '.join([camera.name for camera in cams_no_plan])) + cameras = str(", ".join([camera.name for camera in cams_no_plan])) - err_msg = '''A Ring Protect Plan is required for the''' \ - ''' following cameras: {}.'''.format(cameras) + err_msg = ( + """A Ring Protect Plan is required for the""" + """ following cameras: {}.""".format(cameras) + ) _LOGGER.error(err_msg) hass.components.persistent_notification.async_create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(err_msg), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(err_msg), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) async_add_entities(cams, True) return True @@ -104,26 +106,32 @@ class RingCam(Camera): """Return the state attributes.""" return { ATTR_ATTRIBUTION: CONF_ATTRIBUTION, - 'device_id': self._camera.id, - 'firmware': self._camera.firmware, - 'kind': self._camera.kind, - 'timezone': self._camera.timezone, - 'type': self._camera.family, - 'video_url': self._video_url, + "device_id": self._camera.id, + "firmware": self._camera.firmware, + "kind": self._camera.kind, + "timezone": self._camera.timezone, + "type": self._camera.family, + "video_url": self._video_url, } @asyncio.coroutine def async_camera_image(self): """Return a still image response from the camera.""" from haffmpeg import ImageFrame, IMAGE_JPEG + ffmpeg = ImageFrame(self._ffmpeg.binary, loop=self.hass.loop) if self._video_url is None: return - image = yield from asyncio.shield(ffmpeg.get_image( - self._video_url, output_format=IMAGE_JPEG, - extra_cmd=self._ffmpeg_arguments), loop=self.hass.loop) + image = yield from asyncio.shield( + ffmpeg.get_image( + self._video_url, + output_format=IMAGE_JPEG, + extra_cmd=self._ffmpeg_arguments, + ), + loop=self.hass.loop, + ) return image @asyncio.coroutine @@ -135,12 +143,11 @@ class RingCam(Camera): return stream = CameraMjpeg(self._ffmpeg.binary, loop=self.hass.loop) - yield from stream.open_camera( - self._video_url, extra_cmd=self._ffmpeg_arguments) + yield from stream.open_camera(self._video_url, extra_cmd=self._ffmpeg_arguments) yield from async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self.hass, request, stream, "multipart/x-mixed-replace;boundary=ffserver" + ) yield from stream.close() @property @@ -157,8 +164,7 @@ class RingCam(Camera): last_recording_id = self._camera.last_recording_id - if self._last_video_id != last_recording_id or \ - self._utcnow >= self._expires_at: + if self._last_video_id != last_recording_id or self._utcnow >= self._expires_at: _LOGGER.info("Ring DoorBell properties refreshed") diff --git a/homeassistant/components/camera/rpi_camera.py b/homeassistant/components/camera/rpi_camera.py index ba6f5e933..e88ab318f 100644 --- a/homeassistant/components/camera/rpi_camera.py +++ b/homeassistant/components/camera/rpi_camera.py @@ -12,54 +12,57 @@ from tempfile import NamedTemporaryFile import voluptuous as vol -from homeassistant.components.camera import (Camera, PLATFORM_SCHEMA) -from homeassistant.const import (CONF_NAME, CONF_FILE_PATH, - EVENT_HOMEASSISTANT_STOP) +from homeassistant.components.camera import Camera, PLATFORM_SCHEMA +from homeassistant.const import CONF_NAME, CONF_FILE_PATH, EVENT_HOMEASSISTANT_STOP from homeassistant.helpers import config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_HORIZONTAL_FLIP = 'horizontal_flip' -CONF_IMAGE_HEIGHT = 'image_height' -CONF_IMAGE_QUALITY = 'image_quality' -CONF_IMAGE_ROTATION = 'image_rotation' -CONF_IMAGE_WIDTH = 'image_width' -CONF_TIMELAPSE = 'timelapse' -CONF_VERTICAL_FLIP = 'vertical_flip' +CONF_HORIZONTAL_FLIP = "horizontal_flip" +CONF_IMAGE_HEIGHT = "image_height" +CONF_IMAGE_QUALITY = "image_quality" +CONF_IMAGE_ROTATION = "image_rotation" +CONF_IMAGE_WIDTH = "image_width" +CONF_TIMELAPSE = "timelapse" +CONF_VERTICAL_FLIP = "vertical_flip" DEFAULT_HORIZONTAL_FLIP = 0 DEFAULT_IMAGE_HEIGHT = 480 DEFAULT_IMAGE_QUALITY = 7 DEFAULT_IMAGE_ROTATION = 0 DEFAULT_IMAGE_WIDTH = 640 -DEFAULT_NAME = 'Raspberry Pi Camera' +DEFAULT_NAME = "Raspberry Pi Camera" DEFAULT_TIMELAPSE = 1000 DEFAULT_VERTICAL_FLIP = 0 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_FILE_PATH): cv.isfile, - vol.Optional(CONF_HORIZONTAL_FLIP, default=DEFAULT_HORIZONTAL_FLIP): - vol.All(vol.Coerce(int), vol.Range(min=0, max=1)), - vol.Optional(CONF_IMAGE_HEIGHT, default=DEFAULT_IMAGE_HEIGHT): - vol.Coerce(int), - vol.Optional(CONF_IMAGE_QUALITY, default=DEFAULT_IMAGE_QUALITY): - vol.All(vol.Coerce(int), vol.Range(min=0, max=100)), - vol.Optional(CONF_IMAGE_ROTATION, default=DEFAULT_IMAGE_ROTATION): - vol.All(vol.Coerce(int), vol.Range(min=0, max=359)), - vol.Optional(CONF_IMAGE_WIDTH, default=DEFAULT_IMAGE_WIDTH): - vol.Coerce(int), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_TIMELAPSE, default=1000): vol.Coerce(int), - vol.Optional(CONF_VERTICAL_FLIP, default=DEFAULT_VERTICAL_FLIP): - vol.All(vol.Coerce(int), vol.Range(min=0, max=1)), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_FILE_PATH): cv.isfile, + vol.Optional(CONF_HORIZONTAL_FLIP, default=DEFAULT_HORIZONTAL_FLIP): vol.All( + vol.Coerce(int), vol.Range(min=0, max=1) + ), + vol.Optional(CONF_IMAGE_HEIGHT, default=DEFAULT_IMAGE_HEIGHT): vol.Coerce(int), + vol.Optional(CONF_IMAGE_QUALITY, default=DEFAULT_IMAGE_QUALITY): vol.All( + vol.Coerce(int), vol.Range(min=0, max=100) + ), + vol.Optional(CONF_IMAGE_ROTATION, default=DEFAULT_IMAGE_ROTATION): vol.All( + vol.Coerce(int), vol.Range(min=0, max=359) + ), + vol.Optional(CONF_IMAGE_WIDTH, default=DEFAULT_IMAGE_WIDTH): vol.Coerce(int), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_TIMELAPSE, default=1000): vol.Coerce(int), + vol.Optional(CONF_VERTICAL_FLIP, default=DEFAULT_VERTICAL_FLIP): vol.All( + vol.Coerce(int), vol.Range(min=0, max=1) + ), + } +) def kill_raspistill(*args): """Kill any previously running raspistill process..""" - subprocess.Popen(['killall', 'raspistill'], - stdout=subprocess.DEVNULL, - stderr=subprocess.STDOUT) + subprocess.Popen( + ["killall", "raspistill"], stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT + ) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -68,19 +71,17 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error("'raspistill' was not found") return False - setup_config = ( - { - CONF_NAME: config.get(CONF_NAME), - CONF_IMAGE_WIDTH: config.get(CONF_IMAGE_WIDTH), - CONF_IMAGE_HEIGHT: config.get(CONF_IMAGE_HEIGHT), - CONF_IMAGE_QUALITY: config.get(CONF_IMAGE_QUALITY), - CONF_IMAGE_ROTATION: config.get(CONF_IMAGE_ROTATION), - CONF_TIMELAPSE: config.get(CONF_TIMELAPSE), - CONF_HORIZONTAL_FLIP: config.get(CONF_HORIZONTAL_FLIP), - CONF_VERTICAL_FLIP: config.get(CONF_VERTICAL_FLIP), - CONF_FILE_PATH: config.get(CONF_FILE_PATH) - } - ) + setup_config = { + CONF_NAME: config.get(CONF_NAME), + CONF_IMAGE_WIDTH: config.get(CONF_IMAGE_WIDTH), + CONF_IMAGE_HEIGHT: config.get(CONF_IMAGE_HEIGHT), + CONF_IMAGE_QUALITY: config.get(CONF_IMAGE_QUALITY), + CONF_IMAGE_ROTATION: config.get(CONF_IMAGE_ROTATION), + CONF_TIMELAPSE: config.get(CONF_TIMELAPSE), + CONF_HORIZONTAL_FLIP: config.get(CONF_HORIZONTAL_FLIP), + CONF_VERTICAL_FLIP: config.get(CONF_VERTICAL_FLIP), + CONF_FILE_PATH: config.get(CONF_FILE_PATH), + } hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, kill_raspistill) @@ -95,7 +96,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): # If no file path is defined, use a temporary file if file_path is None: - temp_file = NamedTemporaryFile(suffix='.jpg', delete=False) + temp_file = NamedTemporaryFile(suffix=".jpg", delete=False) temp_file.close() file_path = temp_file.name setup_config[CONF_FILE_PATH] = file_path @@ -123,12 +124,22 @@ class RaspberryCamera(Camera): kill_raspistill() cmd_args = [ - 'raspistill', '--nopreview', '-o', device_info[CONF_FILE_PATH], - '-t', '0', '-w', str(device_info[CONF_IMAGE_WIDTH]), - '-h', str(device_info[CONF_IMAGE_HEIGHT]), - '-tl', str(device_info[CONF_TIMELAPSE]), - '-q', str(device_info[CONF_IMAGE_QUALITY]), - '-rot', str(device_info[CONF_IMAGE_ROTATION]) + "raspistill", + "--nopreview", + "-o", + device_info[CONF_FILE_PATH], + "-t", + "0", + "-w", + str(device_info[CONF_IMAGE_WIDTH]), + "-h", + str(device_info[CONF_IMAGE_HEIGHT]), + "-tl", + str(device_info[CONF_TIMELAPSE]), + "-q", + str(device_info[CONF_IMAGE_QUALITY]), + "-rot", + str(device_info[CONF_IMAGE_ROTATION]), ] if device_info[CONF_HORIZONTAL_FLIP]: cmd_args.append("-hf") @@ -136,13 +147,11 @@ class RaspberryCamera(Camera): if device_info[CONF_VERTICAL_FLIP]: cmd_args.append("-vf") - subprocess.Popen(cmd_args, - stdout=subprocess.DEVNULL, - stderr=subprocess.STDOUT) + subprocess.Popen(cmd_args, stdout=subprocess.DEVNULL, stderr=subprocess.STDOUT) def camera_image(self): """Return raspistill image response.""" - with open(self._config[CONF_FILE_PATH], 'rb') as file: + with open(self._config[CONF_FILE_PATH], "rb") as file: return file.read() @property diff --git a/homeassistant/components/camera/skybell.py b/homeassistant/components/camera/skybell.py index 9a7d7a069..e77d95ecb 100644 --- a/homeassistant/components/camera/skybell.py +++ b/homeassistant/components/camera/skybell.py @@ -10,10 +10,9 @@ import logging import requests from homeassistant.components.camera import Camera -from homeassistant.components.skybell import ( - DOMAIN as SKYBELL_DOMAIN, SkybellDevice) +from homeassistant.components.skybell import DOMAIN as SKYBELL_DOMAIN, SkybellDevice -DEPENDENCIES = ['skybell'] +DEPENDENCIES = ["skybell"] _LOGGER = logging.getLogger(__name__) @@ -55,8 +54,7 @@ class SkybellCamera(SkybellDevice, Camera): self._url = self._device.image try: - self._response = requests.get( - self._url, stream=True, timeout=10) + self._response = requests.get(self._url, stream=True, timeout=10) except requests.HTTPError as err: _LOGGER.warning("Failed to get camera image: %s", err) self._response = None diff --git a/homeassistant/components/camera/synology.py b/homeassistant/components/camera/synology.py index 3e587fff2..d9371449b 100644 --- a/homeassistant/components/camera/synology.py +++ b/homeassistant/components/camera/synology.py @@ -11,48 +11,56 @@ import requests import voluptuous as vol from homeassistant.const import ( - CONF_NAME, CONF_USERNAME, CONF_PASSWORD, - CONF_URL, CONF_WHITELIST, CONF_VERIFY_SSL, CONF_TIMEOUT) -from homeassistant.components.camera import ( - Camera, PLATFORM_SCHEMA) + CONF_NAME, + CONF_USERNAME, + CONF_PASSWORD, + CONF_URL, + CONF_WHITELIST, + CONF_VERIFY_SSL, + CONF_TIMEOUT, +) +from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.helpers.aiohttp_client import ( async_aiohttp_proxy_web, - async_get_clientsession) + async_get_clientsession, +) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['py-synology==0.2.0'] +REQUIREMENTS = ["py-synology==0.2.0"] _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'Synology Camera' +DEFAULT_NAME = "Synology Camera" DEFAULT_TIMEOUT = 5 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_URL): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - vol.Optional(CONF_WHITELIST, default=[]): cv.ensure_list, - vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_URL): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + vol.Optional(CONF_WHITELIST, default=[]): cv.ensure_list, + vol.Optional(CONF_VERIFY_SSL, default=True): cv.boolean, + } +) @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 a Synology IP Camera.""" verify_ssl = config.get(CONF_VERIFY_SSL) timeout = config.get(CONF_TIMEOUT) try: from synology.surveillance_station import SurveillanceStation + surveillance = SurveillanceStation( config.get(CONF_URL), config.get(CONF_USERNAME), config.get(CONF_PASSWORD), verify_ssl=verify_ssl, - timeout=timeout + timeout=timeout, ) except (requests.exceptions.RequestException, ValueError): _LOGGER.exception("Error when initializing SurveillanceStation") @@ -116,7 +124,8 @@ class SynologyCamera(Camera): self._surveillance.update() self._camera = self._surveillance.get_camera(self._camera.camera_id) self._motion_setting = self._surveillance.get_motion_setting( - self._camera.camera_id) + self._camera.camera_id + ) self.is_streaming = self._camera.is_enabled @property diff --git a/homeassistant/components/camera/usps.py b/homeassistant/components/camera/usps.py index d23359d8c..f606139b6 100644 --- a/homeassistant/components/camera/usps.py +++ b/homeassistant/components/camera/usps.py @@ -12,7 +12,7 @@ from homeassistant.components.usps import DATA_USPS _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['usps'] +DEPENDENCIES = ["usps"] SCAN_INTERVAL = timedelta(seconds=10) @@ -60,7 +60,7 @@ class USPSCamera(Camera): self._last_mail = self._usps.mail for article in self._usps.mail: _LOGGER.debug("Fetching article image: %s", article) - img = self._session.get(article['image']).content + img = self._session.get(article["image"]).content self._mail_img.append(img) try: @@ -71,13 +71,13 @@ class USPSCamera(Camera): @property def name(self): """Return the name of this camera.""" - return '{} mail'.format(self._name) + return "{} mail".format(self._name) @property def model(self): """Return date of mail as model.""" try: - return 'Date: {}'.format(str(self._usps.mail[0]['date'])) + return "Date: {}".format(str(self._usps.mail[0]["date"])) except IndexError: return None diff --git a/homeassistant/components/camera/uvc.py b/homeassistant/components/camera/uvc.py index 0e65ac77c..fb546e5a5 100644 --- a/homeassistant/components/camera/uvc.py +++ b/homeassistant/components/camera/uvc.py @@ -15,23 +15,25 @@ from homeassistant.components.camera import Camera, PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv from homeassistant.exceptions import PlatformNotReady -REQUIREMENTS = ['uvcclient==0.10.1'] +REQUIREMENTS = ["uvcclient==0.10.1"] _LOGGER = logging.getLogger(__name__) -CONF_NVR = 'nvr' -CONF_KEY = 'key' -CONF_PASSWORD = 'password' +CONF_NVR = "nvr" +CONF_KEY = "key" +CONF_PASSWORD = "password" -DEFAULT_PASSWORD = 'ubnt' +DEFAULT_PASSWORD = "ubnt" DEFAULT_PORT = 7080 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_NVR): cv.string, - vol.Required(CONF_KEY): cv.string, - vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_NVR): cv.string, + vol.Required(CONF_KEY): cv.string, + vol.Optional(CONF_PASSWORD, default=DEFAULT_PASSWORD): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -42,17 +44,20 @@ def setup_platform(hass, config, add_entities, discovery_info=None): port = config[CONF_PORT] from uvcclient import nvr + try: # Exceptions may be raised in all method calls to the nvr library. nvrconn = nvr.UVCRemote(addr, port, key) cameras = nvrconn.index() - identifier = 'id' if nvrconn.server_version >= (3, 2, 0) else 'uuid' + identifier = "id" if nvrconn.server_version >= (3, 2, 0) else "uuid" # Filter out airCam models, which are not supported in the latest # version of UnifiVideo and which are EOL by Ubiquiti cameras = [ - camera for camera in cameras - if 'airCam' not in nvrconn.get_camera(camera[identifier])['model']] + camera + for camera in cameras + if "airCam" not in nvrconn.get_camera(camera[identifier])["model"] + ] except nvr.NotAuthorized: _LOGGER.error("Authorization failure while connecting to NVR") return False @@ -63,11 +68,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _LOGGER.error("Unable to connect to NVR: %s", str(ex)) raise PlatformNotReady - add_entities([UnifiVideoCamera(nvrconn, - camera[identifier], - camera['name'], - password) - for camera in cameras]) + add_entities( + [ + UnifiVideoCamera(nvrconn, camera[identifier], camera["name"], password) + for camera in cameras + ] + ) return True @@ -95,24 +101,24 @@ class UnifiVideoCamera(Camera): def is_recording(self): """Return true if the camera is recording.""" caminfo = self._nvr.get_camera(self._uuid) - return caminfo['recordingSettings']['fullTimeRecordEnabled'] + return caminfo["recordingSettings"]["fullTimeRecordEnabled"] @property def motion_detection_enabled(self): """Camera Motion Detection Status.""" caminfo = self._nvr.get_camera(self._uuid) - return caminfo['recordingSettings']['motionRecordEnabled'] + return caminfo["recordingSettings"]["motionRecordEnabled"] @property def brand(self): """Return the brand of this camera.""" - return 'Ubiquiti' + return "Ubiquiti" @property def model(self): """Return the model of this camera.""" caminfo = self._nvr.get_camera(self._uuid) - return caminfo['model'] + return caminfo["model"] def _login(self): """Login to the camera.""" @@ -122,24 +128,25 @@ class UnifiVideoCamera(Camera): if self._connect_addr: addrs = [self._connect_addr] else: - addrs = [caminfo['host'], caminfo['internalHost']] + addrs = [caminfo["host"], caminfo["internalHost"]] if self._nvr.server_version >= (3, 2, 0): client_cls = uvc_camera.UVCCameraClientV320 else: client_cls = uvc_camera.UVCCameraClient - if caminfo['username'] is None: - caminfo['username'] = 'ubnt' + if caminfo["username"] is None: + caminfo["username"] = "ubnt" camera = None for addr in addrs: try: - camera = client_cls( - addr, caminfo['username'], self._password) + camera = client_cls(addr, caminfo["username"], self._password) camera.login() - _LOGGER.debug("Logged into UVC camera %(name)s via %(addr)s", - dict(name=self._name, addr=addr)) + _LOGGER.debug( + "Logged into UVC camera %(name)s via %(addr)s", + dict(name=self._name, addr=addr), + ) self._connect_addr = addr break except socket.error: @@ -158,6 +165,7 @@ class UnifiVideoCamera(Camera): def camera_image(self): """Return the image of this camera.""" from uvcclient import camera as uvc_camera + if not self._camera: if not self._login(): return @@ -171,8 +179,7 @@ class UnifiVideoCamera(Camera): if retry: self._login() return _get_image(retry=False) - _LOGGER.error( - "Unable to log into camera, unable to get snapshot") + _LOGGER.error("Unable to log into camera, unable to get snapshot") raise return _get_image() @@ -180,10 +187,11 @@ class UnifiVideoCamera(Camera): def set_motion_detection(self, mode): """Set motion detection on or off.""" from uvcclient.nvr import NvrError + if mode is True: - set_mode = 'motion' + set_mode = "motion" else: - set_mode = 'none' + set_mode = "none" try: self._nvr.set_recordmode(self._uuid, set_mode) diff --git a/homeassistant/components/camera/verisure.py b/homeassistant/components/camera/verisure.py index 01e4e82f3..44a2c2379 100644 --- a/homeassistant/components/camera/verisure.py +++ b/homeassistant/components/camera/verisure.py @@ -26,10 +26,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): return False hub.update_overview() smartcams = [] - smartcams.extend([ - VerisureSmartcam(hass, device_label, directory_path) - for device_label in hub.get( - "$.customerImageCameras[*].deviceLabel")]) + smartcams.extend( + [ + VerisureSmartcam(hass, device_label, directory_path) + for device_label in hub.get("$.customerImageCameras[*].deviceLabel") + ] + ) add_entities(smartcams) @@ -44,8 +46,7 @@ class VerisureSmartcam(Camera): self._directory_path = directory_path self._image = None self._image_id = None - hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, - self.delete_image) + hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, self.delete_image) def camera_image(self): """Return image response.""" @@ -54,26 +55,26 @@ class VerisureSmartcam(Camera): _LOGGER.debug("No image to display") return _LOGGER.debug("Trying to open %s", self._image) - with open(self._image, 'rb') as file: + with open(self._image, "rb") as file: return file.read() def check_imagelist(self): """Check the contents of the image list.""" hub.update_smartcam_imageseries() image_ids = hub.get_image_info( - "$.imageSeries[?(@.deviceLabel=='%s')].image[0].imageId", - self._device_label) + "$.imageSeries[?(@.deviceLabel=='%s')].image[0].imageId", self._device_label + ) if not image_ids: return new_image_id = image_ids[0] - if new_image_id in ('-1', self._image_id): + if new_image_id in ("-1", self._image_id): _LOGGER.debug("The image is the same, or loading image_id") return _LOGGER.debug("Download new image %s", new_image_id) new_image_path = os.path.join( - self._directory_path, '{}{}'.format(new_image_id, '.jpg')) - hub.session.download_image( - self._device_label, new_image_id, new_image_path) + self._directory_path, "{}{}".format(new_image_id, ".jpg") + ) + hub.session.download_image(self._device_label, new_image_id, new_image_path) _LOGGER.debug("Old image_id=%s", self._image_id) self.delete_image(self) @@ -83,7 +84,8 @@ class VerisureSmartcam(Camera): def delete_image(self, event): """Delete an old image.""" remove_image = os.path.join( - self._directory_path, '{}{}'.format(self._image_id, '.jpg')) + self._directory_path, "{}{}".format(self._image_id, ".jpg") + ) try: os.remove(remove_image) _LOGGER.debug("Deleting old image %s", remove_image) @@ -95,5 +97,5 @@ class VerisureSmartcam(Camera): def name(self): """Return the name of this camera.""" return hub.get_first( - "$.customerImageCameras[?(@.deviceLabel=='%s')].area", - self._device_label) + "$.customerImageCameras[?(@.deviceLabel=='%s')].area", self._device_label + ) diff --git a/homeassistant/components/camera/xeoma.py b/homeassistant/components/camera/xeoma.py index c268c3533..909b37f1a 100644 --- a/homeassistant/components/camera/xeoma.py +++ b/homeassistant/components/camera/xeoma.py @@ -9,39 +9,43 @@ import logging import voluptuous as vol from homeassistant.components.camera import PLATFORM_SCHEMA, Camera -from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME) +from homeassistant.const import CONF_HOST, CONF_NAME, CONF_PASSWORD, CONF_USERNAME from homeassistant.helpers import config_validation as cv -REQUIREMENTS = ['pyxeoma==1.4.0'] +REQUIREMENTS = ["pyxeoma==1.4.0"] _LOGGER = logging.getLogger(__name__) -CONF_CAMERAS = 'cameras' -CONF_HIDE = 'hide' -CONF_IMAGE_NAME = 'image_name' -CONF_NEW_VERSION = 'new_version' -CONF_VIEWER_PASSWORD = 'viewer_password' -CONF_VIEWER_USERNAME = 'viewer_username' +CONF_CAMERAS = "cameras" +CONF_HIDE = "hide" +CONF_IMAGE_NAME = "image_name" +CONF_NEW_VERSION = "new_version" +CONF_VIEWER_PASSWORD = "viewer_password" +CONF_VIEWER_USERNAME = "viewer_username" -CAMERAS_SCHEMA = vol.Schema({ - vol.Required(CONF_IMAGE_NAME): cv.string, - vol.Optional(CONF_HIDE, default=False): cv.boolean, - vol.Optional(CONF_NAME): cv.string, -}, required=False) +CAMERAS_SCHEMA = vol.Schema( + { + vol.Required(CONF_IMAGE_NAME): cv.string, + vol.Optional(CONF_HIDE, default=False): cv.boolean, + vol.Optional(CONF_NAME): cv.string, + }, + required=False, +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_CAMERAS): - vol.Schema(vol.All(cv.ensure_list, [CAMERAS_SCHEMA])), - vol.Optional(CONF_NEW_VERSION, default=True): cv.boolean, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_USERNAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_CAMERAS): vol.Schema( + vol.All(cv.ensure_list, [CAMERAS_SCHEMA]) + ), + vol.Optional(CONF_NEW_VERSION, default=True): cv.boolean, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_USERNAME): 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): """Discover and setup Xeoma Cameras.""" from pyxeoma.xeoma import Xeoma, XeomaError @@ -60,16 +64,20 @@ async def async_setup_platform(hass, config, async_add_entities, CONF_HIDE: False, CONF_NAME: image_name, CONF_VIEWER_USERNAME: username, - CONF_VIEWER_PASSWORD: pw - + CONF_VIEWER_PASSWORD: pw, } for image_name, username, pw in discovered_image_names ] for cam in config.get(CONF_CAMERAS, []): camera = next( - (dc for dc in discovered_cameras - if dc[CONF_IMAGE_NAME] == cam[CONF_IMAGE_NAME]), None) + ( + dc + for dc in discovered_cameras + if dc[CONF_IMAGE_NAME] == cam[CONF_IMAGE_NAME] + ), + None, + ) if camera is not None: if CONF_NAME in cam: @@ -79,9 +87,17 @@ async def async_setup_platform(hass, config, async_add_entities, cameras = list(filter(lambda c: not c[CONF_HIDE], discovered_cameras)) async_add_entities( - [XeomaCamera(xeoma, camera[CONF_IMAGE_NAME], camera[CONF_NAME], - camera[CONF_VIEWER_USERNAME], - camera[CONF_VIEWER_PASSWORD]) for camera in cameras]) + [ + XeomaCamera( + xeoma, + camera[CONF_IMAGE_NAME], + camera[CONF_NAME], + camera[CONF_VIEWER_USERNAME], + camera[CONF_VIEWER_PASSWORD], + ) + for camera in cameras + ] + ) except XeomaError as err: _LOGGER.error("Error: %s", err.message) return @@ -103,9 +119,11 @@ class XeomaCamera(Camera): async def async_camera_image(self): """Return a still image response from the camera.""" from pyxeoma.xeoma import XeomaError + try: image = await self._xeoma.async_get_camera_image( - self._image, self._username, self._password) + self._image, self._username, self._password + ) self._last_image = image except XeomaError as err: _LOGGER.error("Error fetching image: %s", err.message) diff --git a/homeassistant/components/camera/xiaomi.py b/homeassistant/components/camera/xiaomi.py index da36299a2..83bfe9c37 100644 --- a/homeassistant/components/camera/xiaomi.py +++ b/homeassistant/components/camera/xiaomi.py @@ -11,44 +11,48 @@ import voluptuous as vol from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.components.ffmpeg import DATA_FFMPEG -from homeassistant.const import (CONF_HOST, CONF_NAME, CONF_PATH, - CONF_PASSWORD, CONF_PORT, CONF_USERNAME) +from homeassistant.const import ( + CONF_HOST, + CONF_NAME, + CONF_PATH, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream -DEPENDENCIES = ['ffmpeg'] +DEPENDENCIES = ["ffmpeg"] _LOGGER = logging.getLogger(__name__) -DEFAULT_BRAND = 'Xiaomi Home Camera' -DEFAULT_PATH = '/media/mmcblk0p1/record' +DEFAULT_BRAND = "Xiaomi Home Camera" +DEFAULT_PATH = "/media/mmcblk0p1/record" DEFAULT_PORT = 21 -DEFAULT_USERNAME = 'root' +DEFAULT_USERNAME = "root" -CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' -CONF_MODEL = 'model' +CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments" +CONF_MODEL = "model" -MODEL_YI = 'yi' -MODEL_XIAOFANG = 'xiaofang' +MODEL_YI = "yi" +MODEL_XIAOFANG = "xiaofang" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_MODEL): vol.Any(MODEL_YI, - MODEL_XIAOFANG), - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string, - vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_MODEL): vol.Any(MODEL_YI, MODEL_XIAOFANG), + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string, + vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string, + vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_FFMPEG_ARGUMENTS): 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 a Xiaomi Camera.""" - _LOGGER.debug('Received configuration for model %s', config[CONF_MODEL]) + _LOGGER.debug("Received configuration for model %s", config[CONF_MODEL]) async_add_entities([XiaomiCamera(hass, config)]) @@ -93,16 +97,16 @@ class XiaomiCamera(Camera): try: ftp.login(self.user, self.passwd) except error_perm as exc: - _LOGGER.error('Camera login failed: %s', exc) + _LOGGER.error("Camera login failed: %s", exc) return False try: ftp.cwd(self.path) except error_perm as exc: - _LOGGER.error('Unable to find path: %s - %s', self.path, exc) + _LOGGER.error("Unable to find path: %s - %s", self.path, exc) return False - dirs = [d for d in ftp.nlst() if '.' not in d] + dirs = [d for d in ftp.nlst() if "." not in d] if not dirs: _LOGGER.warning("There don't appear to be any folders") return False @@ -111,11 +115,11 @@ class XiaomiCamera(Camera): try: ftp.cwd(first_dir) except error_perm as exc: - _LOGGER.error('Unable to find path: %s - %s', first_dir, exc) + _LOGGER.error("Unable to find path: %s - %s", first_dir, exc) return False if self._model == MODEL_XIAOFANG: - dirs = [d for d in ftp.nlst() if '.' not in d] + dirs = [d for d in ftp.nlst() if "." not in d] if not dirs: _LOGGER.warning("There don't appear to be any uploaded videos") return False @@ -123,7 +127,7 @@ class XiaomiCamera(Camera): latest_dir = dirs[-1] ftp.cwd(latest_dir) - videos = [v for v in ftp.nlst() if '.tmp' not in v] + videos = [v for v in ftp.nlst() if ".tmp" not in v] if not videos: _LOGGER.info('Video folder "%s" is empty; delaying', latest_dir) return False @@ -133,8 +137,9 @@ class XiaomiCamera(Camera): else: video = videos[-1] - return 'ftp://{0}:{1}@{2}:{3}{4}/{5}'.format( - self.user, self.passwd, self.host, self.port, ftp.pwd(), video) + return "ftp://{0}:{1}@{2}:{3}{4}/{5}".format( + self.user, self.passwd, self.host, self.port, ftp.pwd(), video + ) async def async_camera_image(self): """Return a still image response from the camera.""" @@ -143,9 +148,12 @@ class XiaomiCamera(Camera): url = await self.hass.async_add_job(self.get_latest_video_url) if url != self._last_url: ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop) - self._last_image = await asyncio.shield(ffmpeg.get_image( - url, output_format=IMAGE_JPEG, - extra_cmd=self._extra_arguments), loop=self.hass.loop) + self._last_image = await asyncio.shield( + ffmpeg.get_image( + url, output_format=IMAGE_JPEG, extra_cmd=self._extra_arguments + ), + loop=self.hass.loop, + ) self._last_url = url return self._last_image @@ -155,10 +163,9 @@ class XiaomiCamera(Camera): from haffmpeg import CameraMjpeg stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop) - await stream.open_camera( - self._last_url, extra_cmd=self._extra_arguments) + await stream.open_camera(self._last_url, extra_cmd=self._extra_arguments) await async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self.hass, request, stream, "multipart/x-mixed-replace;boundary=ffserver" + ) await stream.close() diff --git a/homeassistant/components/camera/yi.py b/homeassistant/components/camera/yi.py index eb26c1cc8..8cb8e9c13 100644 --- a/homeassistant/components/camera/yi.py +++ b/homeassistant/components/camera/yi.py @@ -12,36 +12,43 @@ import voluptuous as vol from homeassistant.components.camera import Camera, PLATFORM_SCHEMA from homeassistant.components.ffmpeg import DATA_FFMPEG from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PATH, CONF_PASSWORD, CONF_PORT, CONF_USERNAME) + CONF_HOST, + CONF_NAME, + CONF_PATH, + CONF_PASSWORD, + CONF_PORT, + CONF_USERNAME, +) from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_aiohttp_proxy_stream from homeassistant.exceptions import PlatformNotReady -REQUIREMENTS = ['aioftp==0.10.1'] -DEPENDENCIES = ['ffmpeg'] +REQUIREMENTS = ["aioftp==0.10.1"] +DEPENDENCIES = ["ffmpeg"] _LOGGER = logging.getLogger(__name__) -DEFAULT_BRAND = 'YI Home Camera' -DEFAULT_PASSWORD = '' -DEFAULT_PATH = '/tmp/sd/record' +DEFAULT_BRAND = "YI Home Camera" +DEFAULT_PASSWORD = "" +DEFAULT_PATH = "/tmp/sd/record" DEFAULT_PORT = 21 -DEFAULT_USERNAME = 'root' +DEFAULT_USERNAME = "root" -CONF_FFMPEG_ARGUMENTS = 'ffmpeg_arguments' +CONF_FFMPEG_ARGUMENTS = "ffmpeg_arguments" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string, - vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string, - vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_FFMPEG_ARGUMENTS): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.string, + vol.Optional(CONF_PATH, default=DEFAULT_PATH): cv.string, + vol.Optional(CONF_USERNAME, default=DEFAULT_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_FFMPEG_ARGUMENTS): 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 a Yi Camera.""" async_add_entities([YiCamera(hass, config)], True) @@ -94,7 +101,7 @@ class YiCamera(Camera): await ftp.change_directory(self.path) dirs = [] for path, attrs in await ftp.list(): - if attrs['type'] == 'dir' and '.' not in str(path): + if attrs["type"] == "dir" and "." not in str(path): dirs.append(path) latest_dir = dirs[-1] await ftp.change_directory(latest_dir) @@ -108,11 +115,17 @@ class YiCamera(Camera): await ftp.quit() self._is_on = True - return 'ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}'.format( - self.user, self.passwd, self.host, self.port, self.path, - latest_dir, videos[-1]) + return "ftp://{0}:{1}@{2}:{3}{4}/{5}/{6}".format( + self.user, + self.passwd, + self.host, + self.port, + self.path, + latest_dir, + videos[-1], + ) except (ConnectionRefusedError, StatusCodeError) as err: - _LOGGER.error('Error while fetching video: %s', err) + _LOGGER.error("Error while fetching video: %s", err) self._is_on = False return None @@ -125,10 +138,10 @@ class YiCamera(Camera): ffmpeg = ImageFrame(self._manager.binary, loop=self.hass.loop) self._last_image = await asyncio.shield( ffmpeg.get_image( - url, - output_format=IMAGE_JPEG, - extra_cmd=self._extra_arguments), - loop=self.hass.loop) + url, output_format=IMAGE_JPEG, extra_cmd=self._extra_arguments + ), + loop=self.hass.loop, + ) self._last_url = url return self._last_image @@ -141,10 +154,9 @@ class YiCamera(Camera): return stream = CameraMjpeg(self._manager.binary, loop=self.hass.loop) - await stream.open_camera( - self._last_url, extra_cmd=self._extra_arguments) + await stream.open_camera(self._last_url, extra_cmd=self._extra_arguments) await async_aiohttp_proxy_stream( - self.hass, request, stream, - 'multipart/x-mixed-replace;boundary=ffserver') + self.hass, request, stream, "multipart/x-mixed-replace;boundary=ffserver" + ) await stream.close() diff --git a/homeassistant/components/camera/zoneminder.py b/homeassistant/components/camera/zoneminder.py index 55d8d91d3..7c255c108 100644 --- a/homeassistant/components/camera/zoneminder.py +++ b/homeassistant/components/camera/zoneminder.py @@ -8,12 +8,15 @@ import logging from homeassistant.const import CONF_NAME from homeassistant.components.camera.mjpeg import ( - CONF_MJPEG_URL, CONF_STILL_IMAGE_URL, MjpegCamera) + CONF_MJPEG_URL, + CONF_STILL_IMAGE_URL, + MjpegCamera, +) from homeassistant.components.zoneminder import DOMAIN as ZONEMINDER_DOMAIN _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['zoneminder'] +DEPENDENCIES = ["zoneminder"] def setup_platform(hass, config, add_entities, discovery_info=None): @@ -40,7 +43,7 @@ class ZoneMinderCamera(MjpegCamera): device_info = { CONF_NAME: monitor.name, CONF_MJPEG_URL: monitor.mjpeg_image_url, - CONF_STILL_IMAGE_URL: monitor.still_image_url + CONF_STILL_IMAGE_URL: monitor.still_image_url, } super().__init__(device_info) self._is_recording = None diff --git a/homeassistant/components/canary.py b/homeassistant/components/canary.py index 04c33d83f..be21fa3b6 100644 --- a/homeassistant/components/canary.py +++ b/homeassistant/components/canary.py @@ -15,29 +15,32 @@ from homeassistant.const import CONF_USERNAME, CONF_PASSWORD, CONF_TIMEOUT from homeassistant.helpers import discovery from homeassistant.util import Throttle -REQUIREMENTS = ['py-canary==0.5.0'] +REQUIREMENTS = ["py-canary==0.5.0"] _LOGGER = logging.getLogger(__name__) -NOTIFICATION_ID = 'canary_notification' -NOTIFICATION_TITLE = 'Canary Setup' +NOTIFICATION_ID = "canary_notification" +NOTIFICATION_TITLE = "Canary Setup" -DOMAIN = 'canary' -DATA_CANARY = 'canary' +DOMAIN = "canary" +DATA_CANARY = "canary" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=30) DEFAULT_TIMEOUT = 10 -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): cv.positive_int, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -CANARY_COMPONENTS = [ - 'alarm_control_panel', 'camera', 'sensor' -] +CANARY_COMPONENTS = ["alarm_control_panel", "camera", "sensor"] def setup(hass, config): @@ -52,11 +55,12 @@ def setup(hass, config): except (ConnectTimeout, HTTPError) as ex: _LOGGER.error("Unable to connect to Canary service: %s", str(ex)) hass.components.persistent_notification.create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) return False for component in CANARY_COMPONENTS: @@ -71,6 +75,7 @@ class CanaryData: def __init__(self, username, password, timeout): """Init the Canary data object.""" from canary.api import Api + self._api = Api(username, password, timeout) self._locations_by_id = {} @@ -87,12 +92,14 @@ class CanaryData: self._locations_by_id[location_id] = location self._entries_by_location_id[location_id] = self._api.get_entries( - location_id, entry_type="motion", limit=1) + location_id, entry_type="motion", limit=1 + ) for device in location.devices: if device.is_online: - self._readings_by_device_id[device.device_id] = \ - self._api.get_latest_readings(device.device_id) + self._readings_by_device_id[ + device.device_id + ] = self._api.get_latest_readings(device.device_id) @property def locations(self): @@ -114,9 +121,14 @@ class CanaryData: def get_reading(self, device_id, sensor_type): """Return reading for device_id and sensor type.""" readings = self._readings_by_device_id.get(device_id, []) - return next(( - reading.value for reading in readings - if reading.sensor_type == sensor_type), None) + return next( + ( + reading.value + for reading in readings + if reading.sensor_type == sensor_type + ), + None, + ) def set_location_mode(self, location_id, mode_name, is_private=False): """Set location mode.""" diff --git a/homeassistant/components/cast/__init__.py b/homeassistant/components/cast/__init__.py index 6885f2426..6ca34a231 100644 --- a/homeassistant/components/cast/__init__.py +++ b/homeassistant/components/cast/__init__.py @@ -3,8 +3,8 @@ from homeassistant import config_entries from homeassistant.helpers import config_entry_flow -DOMAIN = 'cast' -REQUIREMENTS = ['pychromecast==2.1.0'] +DOMAIN = "cast" +REQUIREMENTS = ["pychromecast==2.1.0"] async def async_setup(hass, config): @@ -14,16 +14,20 @@ async def async_setup(hass, config): hass.data[DOMAIN] = conf or {} if conf is not None: - hass.async_create_task(hass.config_entries.flow.async_init( - DOMAIN, context={'source': config_entries.SOURCE_IMPORT})) + hass.async_create_task( + hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_IMPORT} + ) + ) return True async def async_setup_entry(hass, entry): """Set up Cast from a config entry.""" - hass.async_create_task(hass.config_entries.async_forward_entry_setup( - entry, 'media_player')) + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(entry, "media_player") + ) return True @@ -34,5 +38,4 @@ async def _async_has_devices(hass): return await hass.async_add_executor_job(discover_chromecasts) -config_entry_flow.register_discovery_flow( - DOMAIN, 'Google Cast', _async_has_devices) +config_entry_flow.register_discovery_flow(DOMAIN, "Google Cast", _async_has_devices) diff --git a/homeassistant/components/climate/__init__.py b/homeassistant/components/climate/__init__.py index a3273f67c..2f7e06fde 100644 --- a/homeassistant/components/climate/__init__.py +++ b/homeassistant/components/climate/__init__.py @@ -18,42 +18,50 @@ from homeassistant.helpers.entity import Entity from homeassistant.helpers.config_validation import PLATFORM_SCHEMA # noqa import homeassistant.helpers.config_validation as cv from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_TEMPERATURE, SERVICE_TURN_ON, SERVICE_TURN_OFF, - STATE_ON, STATE_OFF, STATE_UNKNOWN, TEMP_CELSIUS, PRECISION_WHOLE, - PRECISION_TENTHS, ) + ATTR_ENTITY_ID, + ATTR_TEMPERATURE, + SERVICE_TURN_ON, + SERVICE_TURN_OFF, + STATE_ON, + STATE_OFF, + STATE_UNKNOWN, + TEMP_CELSIUS, + PRECISION_WHOLE, + PRECISION_TENTHS, +) DEFAULT_MIN_TEMP = 7 DEFAULT_MAX_TEMP = 35 DEFAULT_MIN_HUMITIDY = 30 DEFAULT_MAX_HUMIDITY = 99 -DOMAIN = 'climate' +DOMAIN = "climate" -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" SCAN_INTERVAL = timedelta(seconds=60) -SERVICE_SET_AWAY_MODE = 'set_away_mode' -SERVICE_SET_AUX_HEAT = 'set_aux_heat' -SERVICE_SET_TEMPERATURE = 'set_temperature' -SERVICE_SET_FAN_MODE = 'set_fan_mode' -SERVICE_SET_HOLD_MODE = 'set_hold_mode' -SERVICE_SET_OPERATION_MODE = 'set_operation_mode' -SERVICE_SET_SWING_MODE = 'set_swing_mode' -SERVICE_SET_HUMIDITY = 'set_humidity' +SERVICE_SET_AWAY_MODE = "set_away_mode" +SERVICE_SET_AUX_HEAT = "set_aux_heat" +SERVICE_SET_TEMPERATURE = "set_temperature" +SERVICE_SET_FAN_MODE = "set_fan_mode" +SERVICE_SET_HOLD_MODE = "set_hold_mode" +SERVICE_SET_OPERATION_MODE = "set_operation_mode" +SERVICE_SET_SWING_MODE = "set_swing_mode" +SERVICE_SET_HUMIDITY = "set_humidity" -STATE_HEAT = 'heat' -STATE_COOL = 'cool' -STATE_IDLE = 'idle' -STATE_AUTO = 'auto' -STATE_MANUAL = 'manual' -STATE_DRY = 'dry' -STATE_FAN_ONLY = 'fan_only' -STATE_ECO = 'eco' -STATE_ELECTRIC = 'electric' -STATE_PERFORMANCE = 'performance' -STATE_HIGH_DEMAND = 'high_demand' -STATE_HEAT_PUMP = 'heat_pump' -STATE_GAS = 'gas' +STATE_HEAT = "heat" +STATE_COOL = "cool" +STATE_IDLE = "idle" +STATE_AUTO = "auto" +STATE_MANUAL = "manual" +STATE_DRY = "dry" +STATE_FAN_ONLY = "fan_only" +STATE_ECO = "eco" +STATE_ELECTRIC = "electric" +STATE_PERFORMANCE = "performance" +STATE_HIGH_DEMAND = "high_demand" +STATE_HEAT_PUMP = "heat_pump" +STATE_GAS = "gas" SUPPORT_TARGET_TEMPERATURE = 1 SUPPORT_TARGET_TEMPERATURE_HIGH = 2 @@ -69,85 +77,94 @@ SUPPORT_AWAY_MODE = 1024 SUPPORT_AUX_HEAT = 2048 SUPPORT_ON_OFF = 4096 -ATTR_CURRENT_TEMPERATURE = 'current_temperature' -ATTR_MAX_TEMP = 'max_temp' -ATTR_MIN_TEMP = 'min_temp' -ATTR_TARGET_TEMP_HIGH = 'target_temp_high' -ATTR_TARGET_TEMP_LOW = 'target_temp_low' -ATTR_TARGET_TEMP_STEP = 'target_temp_step' -ATTR_AWAY_MODE = 'away_mode' -ATTR_AUX_HEAT = 'aux_heat' -ATTR_FAN_MODE = 'fan_mode' -ATTR_FAN_LIST = 'fan_list' -ATTR_CURRENT_HUMIDITY = 'current_humidity' -ATTR_HUMIDITY = 'humidity' -ATTR_MAX_HUMIDITY = 'max_humidity' -ATTR_MIN_HUMIDITY = 'min_humidity' -ATTR_HOLD_MODE = 'hold_mode' -ATTR_OPERATION_MODE = 'operation_mode' -ATTR_OPERATION_LIST = 'operation_list' -ATTR_SWING_MODE = 'swing_mode' -ATTR_SWING_LIST = 'swing_list' +ATTR_CURRENT_TEMPERATURE = "current_temperature" +ATTR_MAX_TEMP = "max_temp" +ATTR_MIN_TEMP = "min_temp" +ATTR_TARGET_TEMP_HIGH = "target_temp_high" +ATTR_TARGET_TEMP_LOW = "target_temp_low" +ATTR_TARGET_TEMP_STEP = "target_temp_step" +ATTR_AWAY_MODE = "away_mode" +ATTR_AUX_HEAT = "aux_heat" +ATTR_FAN_MODE = "fan_mode" +ATTR_FAN_LIST = "fan_list" +ATTR_CURRENT_HUMIDITY = "current_humidity" +ATTR_HUMIDITY = "humidity" +ATTR_MAX_HUMIDITY = "max_humidity" +ATTR_MIN_HUMIDITY = "min_humidity" +ATTR_HOLD_MODE = "hold_mode" +ATTR_OPERATION_MODE = "operation_mode" +ATTR_OPERATION_LIST = "operation_list" +ATTR_SWING_MODE = "swing_mode" +ATTR_SWING_LIST = "swing_list" -CONVERTIBLE_ATTRIBUTE = [ - ATTR_TEMPERATURE, - ATTR_TARGET_TEMP_LOW, - ATTR_TARGET_TEMP_HIGH, -] +CONVERTIBLE_ATTRIBUTE = [ATTR_TEMPERATURE, ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH] _LOGGER = logging.getLogger(__name__) -ON_OFF_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, -}) +ON_OFF_SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) -SET_AWAY_MODE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_AWAY_MODE): cv.boolean, -}) -SET_AUX_HEAT_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_AUX_HEAT): cv.boolean, -}) -SET_TEMPERATURE_SCHEMA = vol.Schema(vol.All( - cv.has_at_least_one_key( - ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW), +SET_AWAY_MODE_SCHEMA = vol.Schema( { - vol.Exclusive(ATTR_TEMPERATURE, 'temperature'): vol.Coerce(float), - vol.Inclusive(ATTR_TARGET_TEMP_HIGH, 'temperature'): vol.Coerce(float), - vol.Inclusive(ATTR_TARGET_TEMP_LOW, 'temperature'): vol.Coerce(float), vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Optional(ATTR_OPERATION_MODE): cv.string, + vol.Required(ATTR_AWAY_MODE): cv.boolean, } -)) -SET_FAN_MODE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_FAN_MODE): cv.string, -}) -SET_HOLD_MODE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_HOLD_MODE): cv.string, -}) -SET_OPERATION_MODE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_OPERATION_MODE): cv.string, -}) -SET_HUMIDITY_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_HUMIDITY): vol.Coerce(float), -}) -SET_SWING_MODE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_SWING_MODE): cv.string, -}) +) +SET_AUX_HEAT_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_AUX_HEAT): cv.boolean, + } +) +SET_TEMPERATURE_SCHEMA = vol.Schema( + vol.All( + cv.has_at_least_one_key( + ATTR_TEMPERATURE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW + ), + { + vol.Exclusive(ATTR_TEMPERATURE, "temperature"): vol.Coerce(float), + vol.Inclusive(ATTR_TARGET_TEMP_HIGH, "temperature"): vol.Coerce(float), + vol.Inclusive(ATTR_TARGET_TEMP_LOW, "temperature"): vol.Coerce(float), + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_OPERATION_MODE): cv.string, + }, + ) +) +SET_FAN_MODE_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_FAN_MODE): cv.string, + } +) +SET_HOLD_MODE_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_HOLD_MODE): cv.string, + } +) +SET_OPERATION_MODE_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_OPERATION_MODE): cv.string, + } +) +SET_HUMIDITY_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_HUMIDITY): vol.Coerce(float), + } +) +SET_SWING_MODE_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_SWING_MODE): cv.string, + } +) @bind_hass def set_away_mode(hass, away_mode, entity_id=None): """Turn all or specified climate devices away mode on.""" - data = { - ATTR_AWAY_MODE: away_mode - } + data = {ATTR_AWAY_MODE: away_mode} if entity_id: data[ATTR_ENTITY_ID] = entity_id @@ -158,9 +175,7 @@ def set_away_mode(hass, away_mode, entity_id=None): @bind_hass def set_hold_mode(hass, hold_mode, entity_id=None): """Set new hold mode.""" - data = { - ATTR_HOLD_MODE: hold_mode - } + data = {ATTR_HOLD_MODE: hold_mode} if entity_id: data[ATTR_ENTITY_ID] = entity_id @@ -171,9 +186,7 @@ def set_hold_mode(hass, hold_mode, entity_id=None): @bind_hass def set_aux_heat(hass, aux_heat, entity_id=None): """Turn all or specified climate devices auxiliary heater on.""" - data = { - ATTR_AUX_HEAT: aux_heat - } + data = {ATTR_AUX_HEAT: aux_heat} if entity_id: data[ATTR_ENTITY_ID] = entity_id @@ -182,18 +195,25 @@ def set_aux_heat(hass, aux_heat, entity_id=None): @bind_hass -def set_temperature(hass, temperature=None, entity_id=None, - target_temp_high=None, target_temp_low=None, - operation_mode=None): +def set_temperature( + hass, + temperature=None, + entity_id=None, + target_temp_high=None, + target_temp_low=None, + operation_mode=None, +): """Set new target temperature.""" kwargs = { - key: value for key, value in [ + key: value + for key, value in [ (ATTR_TEMPERATURE, temperature), (ATTR_TARGET_TEMP_HIGH, target_temp_high), (ATTR_TARGET_TEMP_LOW, target_temp_low), (ATTR_ENTITY_ID, entity_id), - (ATTR_OPERATION_MODE, operation_mode) - ] if value is not None + (ATTR_OPERATION_MODE, operation_mode), + ] + if value is not None } _LOGGER.debug("set_temperature start data=%s", kwargs) hass.services.call(DOMAIN, SERVICE_SET_TEMPERATURE, kwargs) @@ -245,49 +265,42 @@ def set_swing_mode(hass, swing_mode, entity_id=None): async def async_setup(hass, config): """Set up climate devices.""" - component = hass.data[DOMAIN] = \ - EntityComponent(_LOGGER, DOMAIN, hass, SCAN_INTERVAL) + component = hass.data[DOMAIN] = EntityComponent( + _LOGGER, DOMAIN, hass, SCAN_INTERVAL + ) await component.async_setup(config) component.async_register_entity_service( - SERVICE_SET_AWAY_MODE, SET_AWAY_MODE_SCHEMA, - async_service_away_mode + SERVICE_SET_AWAY_MODE, SET_AWAY_MODE_SCHEMA, async_service_away_mode ) component.async_register_entity_service( - SERVICE_SET_HOLD_MODE, SET_HOLD_MODE_SCHEMA, - 'async_set_hold_mode' + SERVICE_SET_HOLD_MODE, SET_HOLD_MODE_SCHEMA, "async_set_hold_mode" ) component.async_register_entity_service( - SERVICE_SET_AUX_HEAT, SET_AUX_HEAT_SCHEMA, - async_service_aux_heat + SERVICE_SET_AUX_HEAT, SET_AUX_HEAT_SCHEMA, async_service_aux_heat ) component.async_register_entity_service( - SERVICE_SET_TEMPERATURE, SET_TEMPERATURE_SCHEMA, - async_service_temperature_set + SERVICE_SET_TEMPERATURE, SET_TEMPERATURE_SCHEMA, async_service_temperature_set ) component.async_register_entity_service( - SERVICE_SET_HUMIDITY, SET_HUMIDITY_SCHEMA, - 'async_set_humidity' + SERVICE_SET_HUMIDITY, SET_HUMIDITY_SCHEMA, "async_set_humidity" ) component.async_register_entity_service( - SERVICE_SET_FAN_MODE, SET_FAN_MODE_SCHEMA, - 'async_set_fan_mode' + SERVICE_SET_FAN_MODE, SET_FAN_MODE_SCHEMA, "async_set_fan_mode" ) component.async_register_entity_service( - SERVICE_SET_OPERATION_MODE, SET_OPERATION_MODE_SCHEMA, - 'async_set_operation_mode' + SERVICE_SET_OPERATION_MODE, + SET_OPERATION_MODE_SCHEMA, + "async_set_operation_mode", ) component.async_register_entity_service( - SERVICE_SET_SWING_MODE, SET_SWING_MODE_SCHEMA, - 'async_set_swing_mode' + SERVICE_SET_SWING_MODE, SET_SWING_MODE_SCHEMA, "async_set_swing_mode" ) component.async_register_entity_service( - SERVICE_TURN_OFF, ON_OFF_SERVICE_SCHEMA, - 'async_turn_off' + SERVICE_TURN_OFF, ON_OFF_SERVICE_SCHEMA, "async_turn_off" ) component.async_register_entity_service( - SERVICE_TURN_ON, ON_OFF_SERVICE_SCHEMA, - 'async_turn_on' + SERVICE_TURN_ON, ON_OFF_SERVICE_SCHEMA, "async_turn_on" ) return True @@ -329,17 +342,23 @@ class ClimateDevice(Entity): """Return the optional state attributes.""" data = { ATTR_CURRENT_TEMPERATURE: show_temp( - self.hass, self.current_temperature, self.temperature_unit, - self.precision), + self.hass, + self.current_temperature, + self.temperature_unit, + self.precision, + ), ATTR_MIN_TEMP: show_temp( - self.hass, self.min_temp, self.temperature_unit, - self.precision), + self.hass, self.min_temp, self.temperature_unit, self.precision + ), ATTR_MAX_TEMP: show_temp( - self.hass, self.max_temp, self.temperature_unit, - self.precision), + self.hass, self.max_temp, self.temperature_unit, self.precision + ), ATTR_TEMPERATURE: show_temp( - self.hass, self.target_temperature, self.temperature_unit, - self.precision), + self.hass, + self.target_temperature, + self.temperature_unit, + self.precision, + ), } supported_features = self.supported_features @@ -348,13 +367,19 @@ class ClimateDevice(Entity): if supported_features & SUPPORT_TARGET_TEMPERATURE_HIGH: data[ATTR_TARGET_TEMP_HIGH] = show_temp( - self.hass, self.target_temperature_high, self.temperature_unit, - self.precision) + self.hass, + self.target_temperature_high, + self.temperature_unit, + self.precision, + ) if supported_features & SUPPORT_TARGET_TEMPERATURE_LOW: data[ATTR_TARGET_TEMP_LOW] = show_temp( - self.hass, self.target_temperature_low, self.temperature_unit, - self.precision) + self.hass, + self.target_temperature_low, + self.temperature_unit, + self.precision, + ) if supported_features & SUPPORT_TARGET_HUMIDITY: data[ATTR_HUMIDITY] = self.target_humidity @@ -493,8 +518,7 @@ class ClimateDevice(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job( - ft.partial(self.set_temperature, **kwargs)) + return self.hass.async_add_job(ft.partial(self.set_temperature, **kwargs)) def set_humidity(self, humidity): """Set new target humidity.""" @@ -625,14 +649,16 @@ class ClimateDevice(Entity): @property def min_temp(self): """Return the minimum temperature.""" - return convert_temperature(DEFAULT_MIN_TEMP, TEMP_CELSIUS, - self.temperature_unit) + return convert_temperature( + DEFAULT_MIN_TEMP, TEMP_CELSIUS, self.temperature_unit + ) @property def max_temp(self): """Return the maximum temperature.""" - return convert_temperature(DEFAULT_MAX_TEMP, TEMP_CELSIUS, - self.temperature_unit) + return convert_temperature( + DEFAULT_MAX_TEMP, TEMP_CELSIUS, self.temperature_unit + ) @property def min_humidity(self): @@ -669,9 +695,7 @@ async def async_service_temperature_set(entity, service): for value, temp in service.data.items(): if value in CONVERTIBLE_ATTRIBUTE: kwargs[value] = convert_temperature( - temp, - hass.config.units.temperature_unit, - entity.temperature_unit + temp, hass.config.units.temperature_unit, entity.temperature_unit ) else: kwargs[value] = temp diff --git a/homeassistant/components/climate/daikin.py b/homeassistant/components/climate/daikin.py index 6743bf034..a9cd83cf5 100644 --- a/homeassistant/components/climate/daikin.py +++ b/homeassistant/components/climate/daikin.py @@ -10,50 +10,63 @@ import re import voluptuous as vol from homeassistant.components.climate import ( - ATTR_CURRENT_TEMPERATURE, ATTR_FAN_MODE, ATTR_OPERATION_MODE, - ATTR_SWING_MODE, PLATFORM_SCHEMA, STATE_AUTO, STATE_COOL, STATE_DRY, - STATE_FAN_ONLY, STATE_HEAT, STATE_OFF, SUPPORT_FAN_MODE, - SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE, SUPPORT_TARGET_TEMPERATURE, - ClimateDevice) + ATTR_CURRENT_TEMPERATURE, + ATTR_FAN_MODE, + ATTR_OPERATION_MODE, + ATTR_SWING_MODE, + PLATFORM_SCHEMA, + STATE_AUTO, + STATE_COOL, + STATE_DRY, + STATE_FAN_ONLY, + STATE_HEAT, + STATE_OFF, + SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, + SUPPORT_SWING_MODE, + SUPPORT_TARGET_TEMPERATURE, + ClimateDevice, +) from homeassistant.components.daikin import ( - ATTR_INSIDE_TEMPERATURE, ATTR_OUTSIDE_TEMPERATURE, ATTR_TARGET_TEMPERATURE, - daikin_api_setup) -from homeassistant.const import ( - ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS) + ATTR_INSIDE_TEMPERATURE, + ATTR_OUTSIDE_TEMPERATURE, + ATTR_TARGET_TEMPERATURE, + daikin_api_setup, +) +from homeassistant.const import ATTR_TEMPERATURE, CONF_HOST, CONF_NAME, TEMP_CELSIUS import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pydaikin==0.4'] +REQUIREMENTS = ["pydaikin==0.4"] _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_HOST): cv.string, vol.Optional(CONF_NAME): cv.string} +) HA_STATE_TO_DAIKIN = { - STATE_FAN_ONLY: 'fan', - STATE_DRY: 'dry', - STATE_COOL: 'cool', - STATE_HEAT: 'hot', - STATE_AUTO: 'auto', - STATE_OFF: 'off', + STATE_FAN_ONLY: "fan", + STATE_DRY: "dry", + STATE_COOL: "cool", + STATE_HEAT: "hot", + STATE_AUTO: "auto", + STATE_OFF: "off", } HA_ATTR_TO_DAIKIN = { - ATTR_OPERATION_MODE: 'mode', - ATTR_FAN_MODE: 'f_rate', - ATTR_SWING_MODE: 'f_dir', - ATTR_INSIDE_TEMPERATURE: 'htemp', - ATTR_OUTSIDE_TEMPERATURE: 'otemp', - ATTR_TARGET_TEMPERATURE: 'stemp' + ATTR_OPERATION_MODE: "mode", + ATTR_FAN_MODE: "f_rate", + ATTR_SWING_MODE: "f_dir", + ATTR_INSIDE_TEMPERATURE: "htemp", + ATTR_OUTSIDE_TEMPERATURE: "otemp", + ATTR_TARGET_TEMPERATURE: "stemp", } def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Daikin HVAC platform.""" if discovery_info is not None: - host = discovery_info.get('ip') + host = discovery_info.get("ip") name = None _LOGGER.debug("Discovered a Daikin AC on %s", host) else: @@ -75,47 +88,42 @@ class DaikinClimate(ClimateDevice): self._api = api self._force_refresh = False self._list = { - ATTR_OPERATION_MODE: list( - map(str.title, set(HA_STATE_TO_DAIKIN.values())) - ), + ATTR_OPERATION_MODE: list(map(str.title, set(HA_STATE_TO_DAIKIN.values()))), ATTR_FAN_MODE: list( map( - str.title, - appliance.daikin_values(HA_ATTR_TO_DAIKIN[ATTR_FAN_MODE]) + str.title, appliance.daikin_values(HA_ATTR_TO_DAIKIN[ATTR_FAN_MODE]) ) ), ATTR_SWING_MODE: list( map( str.title, - appliance.daikin_values(HA_ATTR_TO_DAIKIN[ATTR_SWING_MODE]) + appliance.daikin_values(HA_ATTR_TO_DAIKIN[ATTR_SWING_MODE]), ) ), } - self._supported_features = SUPPORT_TARGET_TEMPERATURE \ - | SUPPORT_OPERATION_MODE + self._supported_features = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE daikin_attr = HA_ATTR_TO_DAIKIN[ATTR_FAN_MODE] if self._api.device.values.get(daikin_attr) is not None: self._supported_features |= SUPPORT_FAN_MODE else: # even devices without support must have a default valid value - self._api.device.values[daikin_attr] = 'A' + self._api.device.values[daikin_attr] = "A" daikin_attr = HA_ATTR_TO_DAIKIN[ATTR_SWING_MODE] if self._api.device.values.get(daikin_attr) is not None: self._supported_features |= SUPPORT_SWING_MODE else: # even devices without support must have a default valid value - self._api.device.values[daikin_attr] = '0' + self._api.device.values[daikin_attr] = "0" def get(self, key): """Retrieve device settings from API library cache.""" value = None cast_to_float = False - if key in [ATTR_TEMPERATURE, ATTR_INSIDE_TEMPERATURE, - ATTR_CURRENT_TEMPERATURE]: + if key in [ATTR_TEMPERATURE, ATTR_INSIDE_TEMPERATURE, ATTR_CURRENT_TEMPERATURE]: key = ATTR_INSIDE_TEMPERATURE daikin_attr = HA_ATTR_TO_DAIKIN.get(key) @@ -137,9 +145,7 @@ class DaikinClimate(ClimateDevice): # Daikin can return also internal states auto-1 or auto-7 # and we need to translate them as AUTO value = re.sub( - '[^a-z]', - '', - self._api.device.represent(daikin_attr)[1] + "[^a-z]", "", self._api.device.represent(daikin_attr)[1] ).title() if value is None: @@ -159,8 +165,12 @@ class DaikinClimate(ClimateDevice): """Set device settings using API.""" values = {} - for attr in [ATTR_TEMPERATURE, ATTR_FAN_MODE, ATTR_SWING_MODE, - ATTR_OPERATION_MODE]: + for attr in [ + ATTR_TEMPERATURE, + ATTR_FAN_MODE, + ATTR_SWING_MODE, + ATTR_OPERATION_MODE, + ]: value = settings.get(attr) if value is None: continue @@ -175,7 +185,7 @@ class DaikinClimate(ClimateDevice): # temperature elif attr == ATTR_TEMPERATURE: try: - values['stemp'] = str(int(value)) + values["stemp"] = str(int(value)) except ValueError: _LOGGER.error("Invalid temperature %s", value) diff --git a/homeassistant/components/climate/demo.py b/homeassistant/components/climate/demo.py index bc0b9bd52..ad223b054 100644 --- a/homeassistant/components/climate/demo.py +++ b/homeassistant/components/climate/demo.py @@ -5,13 +5,23 @@ For more details about this platform, please refer to the documentation https://home-assistant.io/components/demo/ """ from homeassistant.components.climate import ( - ClimateDevice, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_TARGET_HUMIDITY, - SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH, - SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_FAN_MODE, - SUPPORT_OPERATION_MODE, SUPPORT_AUX_HEAT, SUPPORT_SWING_MODE, - SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, - SUPPORT_ON_OFF) + ClimateDevice, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_HUMIDITY, + SUPPORT_TARGET_HUMIDITY_LOW, + SUPPORT_TARGET_HUMIDITY_HIGH, + SUPPORT_AWAY_MODE, + SUPPORT_HOLD_MODE, + SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, + SUPPORT_AUX_HEAT, + SUPPORT_SWING_MODE, + SUPPORT_TARGET_TEMPERATURE_HIGH, + SUPPORT_TARGET_TEMPERATURE_LOW, + SUPPORT_ON_OFF, +) from homeassistant.const import TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE SUPPORT_FLAGS = SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH @@ -19,31 +29,89 @@ SUPPORT_FLAGS = SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo climate devices.""" - add_entities([ - DemoClimate('HeatPump', 68, TEMP_FAHRENHEIT, None, None, 77, - None, None, None, None, 'heat', None, None, - None, True), - DemoClimate('Hvac', 21, TEMP_CELSIUS, True, None, 22, 'On High', - 67, 54, 'Off', 'cool', False, None, None, None), - DemoClimate('Ecobee', None, TEMP_CELSIUS, None, 'home', 23, 'Auto Low', - None, None, 'Auto', 'auto', None, 24, 21, None) - ]) + add_entities( + [ + DemoClimate( + "HeatPump", + 68, + TEMP_FAHRENHEIT, + None, + None, + 77, + None, + None, + None, + None, + "heat", + None, + None, + None, + True, + ), + DemoClimate( + "Hvac", + 21, + TEMP_CELSIUS, + True, + None, + 22, + "On High", + 67, + 54, + "Off", + "cool", + False, + None, + None, + None, + ), + DemoClimate( + "Ecobee", + None, + TEMP_CELSIUS, + None, + "home", + 23, + "Auto Low", + None, + None, + "Auto", + "auto", + None, + 24, + 21, + None, + ), + ] + ) class DemoClimate(ClimateDevice): """Representation of a demo climate device.""" - def __init__(self, name, target_temperature, unit_of_measurement, - away, hold, current_temperature, current_fan_mode, - target_humidity, current_humidity, current_swing_mode, - current_operation, aux, target_temp_high, target_temp_low, - is_on): + def __init__( + self, + name, + target_temperature, + unit_of_measurement, + away, + hold, + current_temperature, + current_fan_mode, + target_humidity, + current_humidity, + current_swing_mode, + current_operation, + aux, + target_temp_high, + target_temp_low, + is_on, + ): """Initialize the climate device.""" self._name = name self._support_flags = SUPPORT_FLAGS if target_temperature is not None: - self._support_flags = \ - self._support_flags | SUPPORT_TARGET_TEMPERATURE + self._support_flags = self._support_flags | SUPPORT_TARGET_TEMPERATURE if away is not None: self._support_flags = self._support_flags | SUPPORT_AWAY_MODE if hold is not None: @@ -51,8 +119,7 @@ class DemoClimate(ClimateDevice): if current_fan_mode is not None: self._support_flags = self._support_flags | SUPPORT_FAN_MODE if target_humidity is not None: - self._support_flags = \ - self._support_flags | SUPPORT_TARGET_HUMIDITY + self._support_flags = self._support_flags | SUPPORT_TARGET_HUMIDITY if current_swing_mode is not None: self._support_flags = self._support_flags | SUPPORT_SWING_MODE if current_operation is not None: @@ -60,11 +127,9 @@ class DemoClimate(ClimateDevice): if aux is not None: self._support_flags = self._support_flags | SUPPORT_AUX_HEAT if target_temp_high is not None: - self._support_flags = \ - self._support_flags | SUPPORT_TARGET_TEMPERATURE_HIGH + self._support_flags = self._support_flags | SUPPORT_TARGET_TEMPERATURE_HIGH if target_temp_low is not None: - self._support_flags = \ - self._support_flags | SUPPORT_TARGET_TEMPERATURE_LOW + self._support_flags = self._support_flags | SUPPORT_TARGET_TEMPERATURE_LOW if is_on is not None: self._support_flags = self._support_flags | SUPPORT_ON_OFF self._target_temperature = target_temperature @@ -78,9 +143,9 @@ class DemoClimate(ClimateDevice): self._current_operation = current_operation self._aux = aux self._current_swing_mode = current_swing_mode - self._fan_list = ['On Low', 'On High', 'Auto Low', 'Auto High', 'Off'] - self._operation_list = ['heat', 'cool', 'auto', 'off'] - self._swing_list = ['Auto', '1', '2', '3', 'Off'] + self._fan_list = ["On Low", "On High", "Auto Low", "Auto High", "Off"] + self._operation_list = ["heat", "cool", "auto", "off"] + self._swing_list = ["Auto", "1", "2", "3", "Off"] self._target_temperature_high = target_temp_high self._target_temperature_low = target_temp_low self._on = is_on @@ -179,8 +244,10 @@ class DemoClimate(ClimateDevice): """Set new target temperatures.""" if kwargs.get(ATTR_TEMPERATURE) is not None: self._target_temperature = kwargs.get(ATTR_TEMPERATURE) - if kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None and \ - kwargs.get(ATTR_TARGET_TEMP_LOW) is not None: + if ( + kwargs.get(ATTR_TARGET_TEMP_HIGH) is not None + and kwargs.get(ATTR_TARGET_TEMP_LOW) is not None + ): self._target_temperature_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) self._target_temperature_low = kwargs.get(ATTR_TARGET_TEMP_LOW) self.schedule_update_ha_state() diff --git a/homeassistant/components/climate/ecobee.py b/homeassistant/components/climate/ecobee.py index 46fc5c297..beac9d47d 100644 --- a/homeassistant/components/climate/ecobee.py +++ b/homeassistant/components/climate/ecobee.py @@ -10,47 +10,76 @@ import voluptuous as vol from homeassistant.components import ecobee from homeassistant.components.climate import ( - DOMAIN, STATE_COOL, STATE_HEAT, STATE_AUTO, STATE_IDLE, ClimateDevice, - ATTR_TARGET_TEMP_LOW, ATTR_TARGET_TEMP_HIGH, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_HUMIDITY_LOW, SUPPORT_TARGET_HUMIDITY_HIGH, - SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_FAN_MODE, - SUPPORT_TARGET_TEMPERATURE_LOW, STATE_OFF) + DOMAIN, + STATE_COOL, + STATE_HEAT, + STATE_AUTO, + STATE_IDLE, + ClimateDevice, + ATTR_TARGET_TEMP_LOW, + ATTR_TARGET_TEMP_HIGH, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_AWAY_MODE, + SUPPORT_HOLD_MODE, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_HUMIDITY_LOW, + SUPPORT_TARGET_HUMIDITY_HIGH, + SUPPORT_AUX_HEAT, + SUPPORT_TARGET_TEMPERATURE_HIGH, + SUPPORT_FAN_MODE, + SUPPORT_TARGET_TEMPERATURE_LOW, + STATE_OFF, +) from homeassistant.const import ( - ATTR_ENTITY_ID, STATE_ON, ATTR_TEMPERATURE, TEMP_FAHRENHEIT) + ATTR_ENTITY_ID, + STATE_ON, + ATTR_TEMPERATURE, + TEMP_FAHRENHEIT, +) import homeassistant.helpers.config_validation as cv _CONFIGURING = {} _LOGGER = logging.getLogger(__name__) -ATTR_FAN_MIN_ON_TIME = 'fan_min_on_time' -ATTR_RESUME_ALL = 'resume_all' +ATTR_FAN_MIN_ON_TIME = "fan_min_on_time" +ATTR_RESUME_ALL = "resume_all" DEFAULT_RESUME_ALL = False -TEMPERATURE_HOLD = 'temp' -VACATION_HOLD = 'vacation' -AWAY_MODE = 'awayMode' +TEMPERATURE_HOLD = "temp" +VACATION_HOLD = "vacation" +AWAY_MODE = "awayMode" -DEPENDENCIES = ['ecobee'] +DEPENDENCIES = ["ecobee"] -SERVICE_SET_FAN_MIN_ON_TIME = 'ecobee_set_fan_min_on_time' -SERVICE_RESUME_PROGRAM = 'ecobee_resume_program' +SERVICE_SET_FAN_MIN_ON_TIME = "ecobee_set_fan_min_on_time" +SERVICE_RESUME_PROGRAM = "ecobee_resume_program" -SET_FAN_MIN_ON_TIME_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_FAN_MIN_ON_TIME): vol.Coerce(int), -}) +SET_FAN_MIN_ON_TIME_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Required(ATTR_FAN_MIN_ON_TIME): vol.Coerce(int), + } +) -RESUME_PROGRAM_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Optional(ATTR_RESUME_ALL, default=DEFAULT_RESUME_ALL): cv.boolean, -}) +RESUME_PROGRAM_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_RESUME_ALL, default=DEFAULT_RESUME_ALL): cv.boolean, + } +) -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE | - SUPPORT_HOLD_MODE | SUPPORT_OPERATION_MODE | - SUPPORT_TARGET_HUMIDITY_LOW | SUPPORT_TARGET_HUMIDITY_HIGH | - SUPPORT_AUX_HEAT | SUPPORT_TARGET_TEMPERATURE_HIGH | - SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_FAN_MODE) +SUPPORT_FLAGS = ( + SUPPORT_TARGET_TEMPERATURE + | SUPPORT_AWAY_MODE + | SUPPORT_HOLD_MODE + | SUPPORT_OPERATION_MODE + | SUPPORT_TARGET_HUMIDITY_LOW + | SUPPORT_TARGET_HUMIDITY_HIGH + | SUPPORT_AUX_HEAT + | SUPPORT_TARGET_TEMPERATURE_HIGH + | SUPPORT_TARGET_TEMPERATURE_LOW + | SUPPORT_FAN_MODE +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -58,12 +87,14 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if discovery_info is None: return data = ecobee.NETWORK - hold_temp = discovery_info['hold_temp'] + hold_temp = discovery_info["hold_temp"] _LOGGER.info( - "Loading ecobee thermostat component with hold_temp set to %s", - hold_temp) - devices = [Thermostat(data, index, hold_temp) - for index in range(len(data.ecobee.thermostats))] + "Loading ecobee thermostat component with hold_temp set to %s", hold_temp + ) + devices = [ + Thermostat(data, index, hold_temp) + for index in range(len(data.ecobee.thermostats)) + ] add_entities(devices) def fan_min_on_time_set_service(service): @@ -72,8 +103,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): fan_min_on_time = service.data[ATTR_FAN_MIN_ON_TIME] if entity_id: - target_thermostats = [device for device in devices - if device.entity_id in entity_id] + target_thermostats = [ + device for device in devices if device.entity_id in entity_id + ] else: target_thermostats = devices @@ -88,8 +120,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): resume_all = service.data.get(ATTR_RESUME_ALL) if entity_id: - target_thermostats = [device for device in devices - if device.entity_id in entity_id] + target_thermostats = [ + device for device in devices if device.entity_id in entity_id + ] else: target_thermostats = devices @@ -99,12 +132,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): thermostat.schedule_update_ha_state(True) hass.services.register( - DOMAIN, SERVICE_SET_FAN_MIN_ON_TIME, fan_min_on_time_set_service, - schema=SET_FAN_MIN_ON_TIME_SCHEMA) + DOMAIN, + SERVICE_SET_FAN_MIN_ON_TIME, + fan_min_on_time_set_service, + schema=SET_FAN_MIN_ON_TIME_SCHEMA, + ) hass.services.register( - DOMAIN, SERVICE_RESUME_PROGRAM, resume_program_set_service, - schema=RESUME_PROGRAM_SCHEMA) + DOMAIN, + SERVICE_RESUME_PROGRAM, + resume_program_set_service, + schema=RESUME_PROGRAM_SCHEMA, + ) class Thermostat(ClimateDevice): @@ -114,15 +153,13 @@ class Thermostat(ClimateDevice): """Initialize the thermostat.""" self.data = data self.thermostat_index = thermostat_index - self.thermostat = self.data.ecobee.get_thermostat( - self.thermostat_index) - self._name = self.thermostat['name'] + self.thermostat = self.data.ecobee.get_thermostat(self.thermostat_index) + self._name = self.thermostat["name"] self.hold_temp = hold_temp self.vacation = None self._climate_list = self.climate_list - self._operation_list = ['auto', 'auxHeatOnly', 'cool', - 'heat', 'off'] - self._fan_list = ['auto', 'on'] + self._operation_list = ["auto", "auxHeatOnly", "cool", "heat", "off"] + self._fan_list = ["auto", "on"] self.update_without_throttle = False def update(self): @@ -133,8 +170,7 @@ class Thermostat(ClimateDevice): else: self.data.update() - self.thermostat = self.data.ecobee.get_thermostat( - self.thermostat_index) + self.thermostat = self.data.ecobee.get_thermostat(self.thermostat_index) @property def supported_features(self): @@ -144,7 +180,7 @@ class Thermostat(ClimateDevice): @property def name(self): """Return the name of the Ecobee Thermostat.""" - return self.thermostat['name'] + return self.thermostat["name"] @property def temperature_unit(self): @@ -154,20 +190,20 @@ class Thermostat(ClimateDevice): @property def current_temperature(self): """Return the current temperature.""" - return self.thermostat['runtime']['actualTemperature'] / 10.0 + return self.thermostat["runtime"]["actualTemperature"] / 10.0 @property def target_temperature_low(self): """Return the lower bound temperature we try to reach.""" if self.current_operation == STATE_AUTO: - return self.thermostat['runtime']['desiredHeat'] / 10.0 + return self.thermostat["runtime"]["desiredHeat"] / 10.0 return None @property def target_temperature_high(self): """Return the upper bound temperature we try to reach.""" if self.current_operation == STATE_AUTO: - return self.thermostat['runtime']['desiredCool'] / 10.0 + return self.thermostat["runtime"]["desiredCool"] / 10.0 return None @property @@ -176,22 +212,22 @@ class Thermostat(ClimateDevice): if self.current_operation == STATE_AUTO: return None if self.current_operation == STATE_HEAT: - return self.thermostat['runtime']['desiredHeat'] / 10.0 + return self.thermostat["runtime"]["desiredHeat"] / 10.0 if self.current_operation == STATE_COOL: - return self.thermostat['runtime']['desiredCool'] / 10.0 + return self.thermostat["runtime"]["desiredCool"] / 10.0 return None @property def fan(self): """Return the current fan status.""" - if 'fan' in self.thermostat['equipmentStatus']: + if "fan" in self.thermostat["equipmentStatus"]: return STATE_ON return STATE_OFF @property def current_fan_mode(self): """Return the fan setting.""" - return self.thermostat['runtime']['desiredFanMode'] + return self.thermostat["runtime"]["desiredFanMode"] @property def current_hold_mode(self): @@ -206,35 +242,36 @@ class Thermostat(ClimateDevice): @property def _current_hold_mode(self): - events = self.thermostat['events'] + events = self.thermostat["events"] for event in events: - if event['running']: - if event['type'] == 'hold': - if event['holdClimateRef'] == 'away': - if int(event['endDate'][0:4]) - \ - int(event['startDate'][0:4]) <= 1: + if event["running"]: + if event["type"] == "hold": + if event["holdClimateRef"] == "away": + if ( + int(event["endDate"][0:4]) - int(event["startDate"][0:4]) + <= 1 + ): # A temporary hold from away climate is a hold - return 'away' + return "away" # A permanent hold from away climate return AWAY_MODE - if event['holdClimateRef'] != "": + if event["holdClimateRef"] != "": # Any other hold based on climate - return event['holdClimateRef'] + return event["holdClimateRef"] # Any hold not based on a climate is a temp hold return TEMPERATURE_HOLD - if event['type'].startswith('auto'): + if event["type"].startswith("auto"): # All auto modes are treated as holds - return event['type'][4:].lower() - if event['type'] == 'vacation': - self.vacation = event['name'] + return event["type"][4:].lower() + if event["type"] == "vacation": + self.vacation = event["name"] return VACATION_HOLD return None @property def current_operation(self): """Return current operation.""" - if self.operation_mode == 'auxHeatOnly' or \ - self.operation_mode == 'heatPump': + if self.operation_mode == "auxHeatOnly" or self.operation_mode == "heatPump": return STATE_HEAT return self.operation_mode @@ -246,45 +283,45 @@ class Thermostat(ClimateDevice): @property def operation_mode(self): """Return current operation ie. heat, cool, idle.""" - return self.thermostat['settings']['hvacMode'] + return self.thermostat["settings"]["hvacMode"] @property def mode(self): """Return current mode, as the user-visible name.""" - cur = self.thermostat['program']['currentClimateRef'] - climates = self.thermostat['program']['climates'] - current = list(filter(lambda x: x['climateRef'] == cur, climates)) - return current[0]['name'] + cur = self.thermostat["program"]["currentClimateRef"] + climates = self.thermostat["program"]["climates"] + current = list(filter(lambda x: x["climateRef"] == cur, climates)) + return current[0]["name"] @property def fan_min_on_time(self): """Return current fan minimum on time.""" - return self.thermostat['settings']['fanMinOnTime'] + return self.thermostat["settings"]["fanMinOnTime"] @property def device_state_attributes(self): """Return device specific state attributes.""" # Move these to Thermostat Device and make them global - status = self.thermostat['equipmentStatus'] + status = self.thermostat["equipmentStatus"] operation = None - if status == '': + if status == "": operation = STATE_IDLE - elif 'Cool' in status: + elif "Cool" in status: operation = STATE_COOL - elif 'auxHeat' in status: + elif "auxHeat" in status: operation = STATE_HEAT - elif 'heatPump' in status: + elif "heatPump" in status: operation = STATE_HEAT else: operation = status return { - "actual_humidity": self.thermostat['runtime']['actualHumidity'], + "actual_humidity": self.thermostat["runtime"]["actualHumidity"], "fan": self.fan, "climate_mode": self.mode, "operation": operation, "climate_list": self.climate_list, - "fan_min_on_time": self.fan_min_on_time + "fan_min_on_time": self.fan_min_on_time, } @property @@ -295,13 +332,14 @@ class Thermostat(ClimateDevice): @property def is_aux_heat_on(self): """Return true if aux heater.""" - return 'auxHeat' in self.thermostat['equipmentStatus'] + return "auxHeat" in self.thermostat["equipmentStatus"] def turn_away_mode_on(self): """Turn away mode on by setting it on away hold indefinitely.""" if self._current_hold_mode != AWAY_MODE: - self.data.ecobee.set_climate_hold(self.thermostat_index, 'away', - 'indefinite') + self.data.ecobee.set_climate_hold( + self.thermostat_index, "away", "indefinite" + ) self.update_without_throttle = True def turn_away_mode_off(self): @@ -317,10 +355,9 @@ class Thermostat(ClimateDevice): if hold == hold_mode: # no change, so no action required return - if hold_mode == 'None' or hold_mode is None: + if hold_mode == "None" or hold_mode is None: if hold == VACATION_HOLD: - self.data.ecobee.delete_vacation( - self.thermostat_index, self.vacation) + self.data.ecobee.delete_vacation(self.thermostat_index, self.vacation) else: self.data.ecobee.resume_program(self.thermostat_index) else: @@ -328,7 +365,8 @@ class Thermostat(ClimateDevice): self.set_temp_hold(self.current_temperature) else: self.data.ecobee.set_climate_hold( - self.thermostat_index, hold_mode, self.hold_preference()) + self.thermostat_index, hold_mode, self.hold_preference() + ) self.update_without_throttle = True def set_auto_temp_hold(self, heat_temp, cool_temp): @@ -336,22 +374,26 @@ class Thermostat(ClimateDevice): if cool_temp is not None: cool_temp_setpoint = cool_temp else: - cool_temp_setpoint = ( - self.thermostat['runtime']['desiredCool'] / 10.0) + cool_temp_setpoint = self.thermostat["runtime"]["desiredCool"] / 10.0 if heat_temp is not None: heat_temp_setpoint = heat_temp else: - heat_temp_setpoint = ( - self.thermostat['runtime']['desiredCool'] / 10.0) + heat_temp_setpoint = self.thermostat["runtime"]["desiredCool"] / 10.0 - self.data.ecobee.set_hold_temp(self.thermostat_index, - cool_temp_setpoint, heat_temp_setpoint, - self.hold_preference()) - _LOGGER.debug("Setting ecobee hold_temp to: heat=%s, is=%s, " - "cool=%s, is=%s", heat_temp, - isinstance(heat_temp, (int, float)), cool_temp, - isinstance(cool_temp, (int, float))) + self.data.ecobee.set_hold_temp( + self.thermostat_index, + cool_temp_setpoint, + heat_temp_setpoint, + self.hold_preference(), + ) + _LOGGER.debug( + "Setting ecobee hold_temp to: heat=%s, is=%s, " "cool=%s, is=%s", + heat_temp, + isinstance(heat_temp, (int, float)), + cool_temp, + isinstance(cool_temp, (int, float)), + ) self.update_without_throttle = True @@ -362,11 +404,15 @@ class Thermostat(ClimateDevice): _LOGGER.error(error) return - cool_temp = self.thermostat['runtime']['desiredCool'] / 10.0 - heat_temp = self.thermostat['runtime']['desiredHeat'] / 10.0 - self.data.ecobee.set_fan_mode(self.thermostat_index, fan_mode, - cool_temp, heat_temp, - self.hold_preference()) + cool_temp = self.thermostat["runtime"]["desiredCool"] / 10.0 + heat_temp = self.thermostat["runtime"]["desiredHeat"] / 10.0 + self.data.ecobee.set_fan_mode( + self.thermostat_index, + fan_mode, + cool_temp, + heat_temp, + self.hold_preference(), + ) _LOGGER.info("Setting fan mode to: %s", fan_mode) @@ -381,12 +427,11 @@ class Thermostat(ClimateDevice): heatCoolMinDelta property. https://www.ecobee.com/home/developer/api/examples/ex5.shtml """ - if self.current_operation == STATE_HEAT or self.current_operation == \ - STATE_COOL: + if self.current_operation == STATE_HEAT or self.current_operation == STATE_COOL: heat_temp = temp cool_temp = temp else: - delta = self.thermostat['settings']['heatCoolMinDelta'] / 10 + delta = self.thermostat["settings"]["heatCoolMinDelta"] / 10 heat_temp = temp - delta cool_temp = temp + delta self.set_auto_temp_hold(heat_temp, cool_temp) @@ -397,14 +442,14 @@ class Thermostat(ClimateDevice): high_temp = kwargs.get(ATTR_TARGET_TEMP_HIGH) temp = kwargs.get(ATTR_TEMPERATURE) - if self.current_operation == STATE_AUTO and \ - (low_temp is not None or high_temp is not None): + if self.current_operation == STATE_AUTO and ( + low_temp is not None or high_temp is not None + ): self.set_auto_temp_hold(low_temp, high_temp) elif temp is not None: self.set_temp_hold(temp) else: - _LOGGER.error( - "Missing valid arguments for set_temperature in %s", kwargs) + _LOGGER.error("Missing valid arguments for set_temperature in %s", kwargs) def set_humidity(self, humidity): """Set the humidity level.""" @@ -417,30 +462,30 @@ class Thermostat(ClimateDevice): def set_fan_min_on_time(self, fan_min_on_time): """Set the minimum fan on time.""" - self.data.ecobee.set_fan_min_on_time( - self.thermostat_index, fan_min_on_time) + self.data.ecobee.set_fan_min_on_time(self.thermostat_index, fan_min_on_time) self.update_without_throttle = True def resume_program(self, resume_all): """Resume the thermostat schedule program.""" self.data.ecobee.resume_program( - self.thermostat_index, 'true' if resume_all else 'false') + self.thermostat_index, "true" if resume_all else "false" + ) self.update_without_throttle = True def hold_preference(self): """Return user preference setting for hold time.""" # Values returned from thermostat are 'useEndTime4hour', # 'useEndTime2hour', 'nextTransition', 'indefinite', 'askMe' - default = self.thermostat['settings']['holdAction'] - if default == 'nextTransition': + default = self.thermostat["settings"]["holdAction"] + if default == "nextTransition": return default # add further conditions if other hold durations should be # supported; note that this should not include 'indefinite' # as an indefinite away hold is interpreted as away_mode - return 'nextTransition' + return "nextTransition" @property def climate_list(self): """Return the list of climates currently available.""" - climates = self.thermostat['program']['climates'] - return list(map((lambda x: x['name']), climates)) + climates = self.thermostat["program"]["climates"] + return list(map((lambda x: x["name"]), climates)) diff --git a/homeassistant/components/climate/econet.py b/homeassistant/components/climate/econet.py index 8be640c37..1fc4aaa51 100644 --- a/homeassistant/components/climate/econet.py +++ b/homeassistant/components/climate/econet.py @@ -10,60 +10,73 @@ import logging import voluptuous as vol from homeassistant.components.climate import ( - DOMAIN, PLATFORM_SCHEMA, STATE_ECO, STATE_ELECTRIC, STATE_GAS, - STATE_HEAT_PUMP, STATE_HIGH_DEMAND, STATE_OFF, STATE_PERFORMANCE, - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) + DOMAIN, + PLATFORM_SCHEMA, + STATE_ECO, + STATE_ELECTRIC, + STATE_GAS, + STATE_HEAT_PUMP, + STATE_HIGH_DEMAND, + STATE_OFF, + STATE_PERFORMANCE, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + ClimateDevice, +) from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_TEMPERATURE, CONF_PASSWORD, CONF_USERNAME, - TEMP_FAHRENHEIT) + ATTR_ENTITY_ID, + ATTR_TEMPERATURE, + CONF_PASSWORD, + CONF_USERNAME, + TEMP_FAHRENHEIT, +) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyeconet==0.0.6'] +REQUIREMENTS = ["pyeconet==0.0.6"] _LOGGER = logging.getLogger(__name__) -ATTR_VACATION_START = 'next_vacation_start_date' -ATTR_VACATION_END = 'next_vacation_end_date' -ATTR_ON_VACATION = 'on_vacation' -ATTR_TODAYS_ENERGY_USAGE = 'todays_energy_usage' -ATTR_IN_USE = 'in_use' +ATTR_VACATION_START = "next_vacation_start_date" +ATTR_VACATION_END = "next_vacation_end_date" +ATTR_ON_VACATION = "on_vacation" +ATTR_TODAYS_ENERGY_USAGE = "todays_energy_usage" +ATTR_IN_USE = "in_use" -ATTR_START_DATE = 'start_date' -ATTR_END_DATE = 'end_date' +ATTR_START_DATE = "start_date" +ATTR_END_DATE = "end_date" -SUPPORT_FLAGS_HEATER = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE) +SUPPORT_FLAGS_HEATER = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE -SERVICE_ADD_VACATION = 'econet_add_vacation' -SERVICE_DELETE_VACATION = 'econet_delete_vacation' +SERVICE_ADD_VACATION = "econet_add_vacation" +SERVICE_DELETE_VACATION = "econet_delete_vacation" -ADD_VACATION_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Optional(ATTR_START_DATE): cv.positive_int, - vol.Required(ATTR_END_DATE): cv.positive_int, -}) +ADD_VACATION_SCHEMA = vol.Schema( + { + vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, + vol.Optional(ATTR_START_DATE): cv.positive_int, + vol.Required(ATTR_END_DATE): cv.positive_int, + } +) -DELETE_VACATION_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, -}) +DELETE_VACATION_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) -ECONET_DATA = 'econet' +ECONET_DATA = "econet" HA_STATE_TO_ECONET = { - STATE_ECO: 'Energy Saver', - STATE_ELECTRIC: 'Electric', - STATE_HEAT_PUMP: 'Heat Pump', - STATE_GAS: 'gas', - STATE_HIGH_DEMAND: 'High Demand', - STATE_OFF: 'Off', - STATE_PERFORMANCE: 'Performance' + STATE_ECO: "Energy Saver", + STATE_ELECTRIC: "Electric", + STATE_HEAT_PUMP: "Heat Pump", + STATE_GAS: "gas", + STATE_HIGH_DEMAND: "High Demand", + STATE_OFF: "Off", + STATE_PERFORMANCE: "Performance", } ECONET_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_ECONET.items()} -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -71,7 +84,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): from pyeconet.api import PyEcoNet hass.data[ECONET_DATA] = {} - hass.data[ECONET_DATA]['water_heaters'] = [] + hass.data[ECONET_DATA]["water_heaters"] = [] username = config.get(CONF_USERNAME) password = config.get(CONF_PASSWORD) @@ -79,17 +92,18 @@ def setup_platform(hass, config, add_entities, discovery_info=None): econet = PyEcoNet(username, password) water_heaters = econet.get_water_heaters() hass_water_heaters = [ - EcoNetWaterHeater(water_heater) for water_heater in water_heaters] + EcoNetWaterHeater(water_heater) for water_heater in water_heaters + ] add_entities(hass_water_heaters) - hass.data[ECONET_DATA]['water_heaters'].extend(hass_water_heaters) + hass.data[ECONET_DATA]["water_heaters"].extend(hass_water_heaters) def service_handle(service): """Handle the service calls.""" - entity_ids = service.data.get('entity_id') - all_heaters = hass.data[ECONET_DATA]['water_heaters'] + entity_ids = service.data.get("entity_id") + all_heaters = hass.data[ECONET_DATA]["water_heaters"] _heaters = [ - x for x in all_heaters - if not entity_ids or x.entity_id in entity_ids] + x for x in all_heaters if not entity_ids or x.entity_id in entity_ids + ] for _water_heater in _heaters: if service.service == SERVICE_ADD_VACATION: @@ -102,11 +116,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): _water_heater.schedule_update_ha_state(True) - hass.services.register(DOMAIN, SERVICE_ADD_VACATION, service_handle, - schema=ADD_VACATION_SCHEMA) + hass.services.register( + DOMAIN, SERVICE_ADD_VACATION, service_handle, schema=ADD_VACATION_SCHEMA + ) - hass.services.register(DOMAIN, SERVICE_DELETE_VACATION, service_handle, - schema=DELETE_VACATION_SCHEMA) + hass.services.register( + DOMAIN, SERVICE_DELETE_VACATION, service_handle, schema=DELETE_VACATION_SCHEMA + ) class EcoNetWaterHeater(ClimateDevice): @@ -167,8 +183,11 @@ class EcoNetWaterHeater(ClimateDevice): if ha_mode is not None: op_list.append(ha_mode) else: - error = "Invalid operation mode mapping. " + mode + \ - " doesn't map. Please report this." + error = ( + "Invalid operation mode mapping. " + + mode + + " doesn't map. Please report this." + ) _LOGGER.error(error) return op_list diff --git a/homeassistant/components/climate/ephember.py b/homeassistant/components/climate/ephember.py index cd410cf3b..6f3ee744e 100644 --- a/homeassistant/components/climate/ephember.py +++ b/homeassistant/components/climate/ephember.py @@ -9,14 +9,24 @@ from datetime import timedelta import voluptuous as vol from homeassistant.components.climate import ( - ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_OFF, - STATE_AUTO, SUPPORT_AUX_HEAT, SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE) + ClimateDevice, + PLATFORM_SCHEMA, + STATE_HEAT, + STATE_OFF, + STATE_AUTO, + SUPPORT_AUX_HEAT, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, +) from homeassistant.const import ( - TEMP_CELSIUS, CONF_USERNAME, CONF_PASSWORD, ATTR_TEMPERATURE) + TEMP_CELSIUS, + CONF_USERNAME, + CONF_PASSWORD, + ATTR_TEMPERATURE, +) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyephember==0.2.0'] +REQUIREMENTS = ["pyephember==0.2.0"] _LOGGER = logging.getLogger(__name__) @@ -25,16 +35,11 @@ SCAN_INTERVAL = timedelta(seconds=120) OPERATION_LIST = [STATE_AUTO, STATE_HEAT, STATE_OFF] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string} +) -EPH_TO_HA_STATE = { - 'AUTO': STATE_AUTO, - 'ON': STATE_HEAT, - 'OFF': STATE_OFF -} +EPH_TO_HA_STATE = {"AUTO": STATE_AUTO, "ON": STATE_HEAT, "OFF": STATE_OFF} HA_STATE_TO_EPH = {value: key for key, value in EPH_TO_HA_STATE.items()} @@ -64,9 +69,9 @@ class EphEmberThermostat(ClimateDevice): def __init__(self, ember, zone): """Initialize the thermostat.""" self._ember = ember - self._zone_name = zone['name'] + self._zone_name = zone["name"] self._zone = zone - self._hot_water = zone['isHotWater'] + self._hot_water = zone["isHotWater"] @property def supported_features(self): @@ -74,9 +79,7 @@ class EphEmberThermostat(ClimateDevice): if self._hot_water: return SUPPORT_AUX_HEAT | SUPPORT_OPERATION_MODE - return (SUPPORT_TARGET_TEMPERATURE | - SUPPORT_AUX_HEAT | - SUPPORT_OPERATION_MODE) + return SUPPORT_TARGET_TEMPERATURE | SUPPORT_AUX_HEAT | SUPPORT_OPERATION_MODE @property def name(self): @@ -91,12 +94,12 @@ class EphEmberThermostat(ClimateDevice): @property def current_temperature(self): """Return the current temperature.""" - return self._zone['currentTemperature'] + return self._zone["currentTemperature"] @property def target_temperature(self): """Return the temperature we try to reach.""" - return self._zone['targetTemperature'] + return self._zone["targetTemperature"] @property def target_temperature_step(self): @@ -109,9 +112,7 @@ class EphEmberThermostat(ClimateDevice): @property def device_state_attributes(self): """Show Device Attributes.""" - attributes = { - 'currently_active': self._zone['isCurrentlyActive'] - } + attributes = {"currently_active": self._zone["isCurrentlyActive"]} return attributes @property @@ -136,7 +137,7 @@ class EphEmberThermostat(ClimateDevice): @property def is_on(self): """Return current state.""" - if self._zone['isCurrentlyActive']: + if self._zone["isCurrentlyActive"]: return True return None @@ -144,12 +145,13 @@ class EphEmberThermostat(ClimateDevice): @property def is_aux_heat_on(self): """Return true if aux heater.""" - return self._zone['isBoostActive'] + return self._zone["isBoostActive"] def turn_aux_heat_on(self): """Turn auxiliary heater on.""" self._ember.activate_boost_by_name( - self._zone_name, self._zone['targetTemperature']) + self._zone_name, self._zone["targetTemperature"] + ) def turn_aux_heat_off(self): """Turn auxiliary heater off.""" @@ -170,15 +172,14 @@ class EphEmberThermostat(ClimateDevice): if temperature > self.max_temp or temperature < self.min_temp: return - self._ember.set_target_temperture_by_name(self._zone_name, - int(temperature)) + self._ember.set_target_temperture_by_name(self._zone_name, int(temperature)) @property def min_temp(self): """Return the minimum temperature.""" # Hot water temp doesn't support being changed if self._hot_water: - return self._zone['targetTemperature'] + return self._zone["targetTemperature"] return 5 @@ -186,7 +187,7 @@ class EphEmberThermostat(ClimateDevice): def max_temp(self): """Return the maximum temperature.""" if self._hot_water: - return self._zone['targetTemperature'] + return self._zone["targetTemperature"] return 35 @@ -198,6 +199,7 @@ class EphEmberThermostat(ClimateDevice): def map_mode_hass_eph(operation_mode): """Map from home assistant mode to eph mode.""" from pyephember.pyephember import ZoneMode + return getattr(ZoneMode, HA_STATE_TO_EPH.get(operation_mode), None) @staticmethod diff --git a/homeassistant/components/climate/eq3btsmart.py b/homeassistant/components/climate/eq3btsmart.py index 904d8222e..e46e401d7 100644 --- a/homeassistant/components/climate/eq3btsmart.py +++ b/homeassistant/components/climate/eq3btsmart.py @@ -9,37 +9,45 @@ import logging import voluptuous as vol from homeassistant.components.climate import ( - STATE_ON, STATE_OFF, STATE_AUTO, PLATFORM_SCHEMA, ClimateDevice, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE) + STATE_ON, + STATE_OFF, + STATE_AUTO, + PLATFORM_SCHEMA, + ClimateDevice, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_OPERATION_MODE, + SUPPORT_AWAY_MODE, +) from homeassistant.const import ( - CONF_MAC, CONF_DEVICES, TEMP_CELSIUS, ATTR_TEMPERATURE, PRECISION_HALVES) + CONF_MAC, + CONF_DEVICES, + TEMP_CELSIUS, + ATTR_TEMPERATURE, + PRECISION_HALVES, +) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['python-eq3bt==0.1.9', 'construct==2.9.41'] +REQUIREMENTS = ["python-eq3bt==0.1.9", "construct==2.9.41"] _LOGGER = logging.getLogger(__name__) -STATE_BOOST = 'boost' -STATE_AWAY = 'away' -STATE_MANUAL = 'manual' +STATE_BOOST = "boost" +STATE_AWAY = "away" +STATE_MANUAL = "manual" -ATTR_STATE_WINDOW_OPEN = 'window_open' -ATTR_STATE_VALVE = 'valve' -ATTR_STATE_LOCKED = 'is_locked' -ATTR_STATE_LOW_BAT = 'low_battery' -ATTR_STATE_AWAY_END = 'away_end' +ATTR_STATE_WINDOW_OPEN = "window_open" +ATTR_STATE_VALVE = "valve" +ATTR_STATE_LOCKED = "is_locked" +ATTR_STATE_LOW_BAT = "low_battery" +ATTR_STATE_AWAY_END = "away_end" -DEVICE_SCHEMA = vol.Schema({ - vol.Required(CONF_MAC): cv.string, -}) +DEVICE_SCHEMA = vol.Schema({vol.Required(CONF_MAC): cv.string}) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DEVICES): - vol.Schema({cv.string: DEVICE_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_DEVICES): vol.Schema({cv.string: DEVICE_SCHEMA})} +) -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | - SUPPORT_AWAY_MODE) +SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE def setup_platform(hass, config, add_entities, discovery_info=None): @@ -177,19 +185,20 @@ class EQ3BTSmartThermostat(ClimateDevice): def update(self): """Update the data from the thermostat.""" from bluepy.btle import BTLEException + try: self._thermostat.update() except BTLEException as ex: _LOGGER.warning("Updating the state failed: %s", ex) - if (self._target_temperature and - self._thermostat.target_temperature - != self._target_temperature): + if ( + self._target_temperature + and self._thermostat.target_temperature != self._target_temperature + ): self.set_temperature(temperature=self._target_temperature) else: self._target_temperature = None - if (self._target_mode and - self.modes[self._thermostat.mode] != self._target_mode): + if self._target_mode and self.modes[self._thermostat.mode] != self._target_mode: self.set_operation_mode(operation_mode=self._target_mode) else: self._target_mode = None diff --git a/homeassistant/components/climate/flexit.py b/homeassistant/components/climate/flexit.py index de74d2fac..82cdc93eb 100644 --- a/homeassistant/components/climate/flexit.py +++ b/homeassistant/components/climate/flexit.py @@ -15,21 +15,30 @@ import logging import voluptuous as vol from homeassistant.const import ( - CONF_NAME, CONF_SLAVE, TEMP_CELSIUS, - ATTR_TEMPERATURE, DEVICE_DEFAULT_NAME) + CONF_NAME, + CONF_SLAVE, + TEMP_CELSIUS, + ATTR_TEMPERATURE, + DEVICE_DEFAULT_NAME, +) from homeassistant.components.climate import ( - ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_FAN_MODE) + ClimateDevice, + PLATFORM_SCHEMA, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_FAN_MODE, +) from homeassistant.components import modbus import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pyflexit==0.3'] -DEPENDENCIES = ['modbus'] +REQUIREMENTS = ["pyflexit==0.3"] +DEPENDENCIES = ["modbus"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_SLAVE): vol.All(int, vol.Range(min=0, max=32)), - vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_SLAVE): vol.All(int, vol.Range(min=0, max=32)), + vol.Optional(CONF_NAME, default=DEVICE_DEFAULT_NAME): cv.string, + } +) _LOGGER = logging.getLogger(__name__) @@ -49,13 +58,14 @@ class Flexit(ClimateDevice): def __init__(self, modbus_slave, name): """Initialize the unit.""" from pyflexit import pyflexit + self._name = name self._slave = modbus_slave self._target_temperature = None self._current_temperature = None self._current_fan_mode = None self._current_operation = None - self._fan_list = ['Off', 'Low', 'Medium', 'High'] + self._fan_list = ["Off", "Low", "Medium", "High"] self._current_operation = None self._filter_hours = None self._filter_alarm = None @@ -78,8 +88,7 @@ class Flexit(ClimateDevice): self._target_temperature = self.unit.get_target_temp self._current_temperature = self.unit.get_temp - self._current_fan_mode =\ - self._fan_list[self.unit.get_fan_speed] + self._current_fan_mode = self._fan_list[self.unit.get_fan_speed] self._filter_hours = self.unit.get_filter_hours # Mechanical heat recovery, 0-100% self._heat_recovery = self.unit.get_heat_recovery @@ -98,12 +107,12 @@ class Flexit(ClimateDevice): def device_state_attributes(self): """Return device specific state attributes.""" return { - 'filter_hours': self._filter_hours, - 'filter_alarm': self._filter_alarm, - 'heat_recovery': self._heat_recovery, - 'heating': self._heating, - 'heater_enabled': self._heater_enabled, - 'cooling': self._cooling + "filter_hours": self._filter_hours, + "filter_alarm": self._filter_alarm, + "heat_recovery": self._heat_recovery, + "heating": self._heating, + "heater_enabled": self._heater_enabled, + "cooling": self._cooling, } @property diff --git a/homeassistant/components/climate/fritzbox.py b/homeassistant/components/climate/fritzbox.py index 3eedb89a3..c672e4650 100644 --- a/homeassistant/components/climate/fritzbox.py +++ b/homeassistant/components/climate/fritzbox.py @@ -10,18 +10,28 @@ import requests from homeassistant.components.fritzbox import DOMAIN as FRITZBOX_DOMAIN from homeassistant.components.fritzbox import ( - ATTR_STATE_DEVICE_LOCKED, ATTR_STATE_BATTERY_LOW, ATTR_STATE_LOCKED) + ATTR_STATE_DEVICE_LOCKED, + ATTR_STATE_BATTERY_LOW, + ATTR_STATE_LOCKED, +) from homeassistant.components.climate import ( - ATTR_OPERATION_MODE, ClimateDevice, STATE_ECO, STATE_HEAT, STATE_MANUAL, - STATE_OFF, STATE_ON, SUPPORT_OPERATION_MODE, - SUPPORT_TARGET_TEMPERATURE) -from homeassistant.const import ( - ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS) -DEPENDENCIES = ['fritzbox'] + ATTR_OPERATION_MODE, + ClimateDevice, + STATE_ECO, + STATE_HEAT, + STATE_MANUAL, + STATE_OFF, + STATE_ON, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, +) +from homeassistant.const import ATTR_TEMPERATURE, PRECISION_HALVES, TEMP_CELSIUS + +DEPENDENCIES = ["fritzbox"] _LOGGER = logging.getLogger(__name__) -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE) +SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE OPERATION_LIST = [STATE_HEAT, STATE_ECO, STATE_OFF, STATE_ON] @@ -94,8 +104,7 @@ class FritzboxThermostat(ClimateDevice): @property def target_temperature(self): """Return the temperature we try to reach.""" - if self._target_temperature in (ON_API_TEMPERATURE, - OFF_API_TEMPERATURE): + if self._target_temperature in (ON_API_TEMPERATURE, OFF_API_TEMPERATURE): return None return self._target_temperature diff --git a/homeassistant/components/climate/generic_thermostat.py b/homeassistant/components/climate/generic_thermostat.py index 85879b812..09a4845ff 100644 --- a/homeassistant/components/climate/generic_thermostat.py +++ b/homeassistant/components/climate/generic_thermostat.py @@ -12,64 +12,78 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.core import DOMAIN as HA_DOMAIN from homeassistant.components.climate import ( - STATE_HEAT, STATE_COOL, STATE_IDLE, STATE_AUTO, ClimateDevice, - ATTR_OPERATION_MODE, ATTR_AWAY_MODE, SUPPORT_OPERATION_MODE, - SUPPORT_AWAY_MODE, SUPPORT_TARGET_TEMPERATURE, PLATFORM_SCHEMA) + STATE_HEAT, + STATE_COOL, + STATE_IDLE, + STATE_AUTO, + ClimateDevice, + ATTR_OPERATION_MODE, + ATTR_AWAY_MODE, + SUPPORT_OPERATION_MODE, + SUPPORT_AWAY_MODE, + SUPPORT_TARGET_TEMPERATURE, + PLATFORM_SCHEMA, +) from homeassistant.const import ( - STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, ATTR_ENTITY_ID, - SERVICE_TURN_ON, SERVICE_TURN_OFF, STATE_UNKNOWN) + STATE_ON, + STATE_OFF, + ATTR_TEMPERATURE, + CONF_NAME, + ATTR_ENTITY_ID, + SERVICE_TURN_ON, + SERVICE_TURN_OFF, + STATE_UNKNOWN, +) from homeassistant.helpers import condition from homeassistant.helpers.event import ( - async_track_state_change, async_track_time_interval) + async_track_state_change, + async_track_time_interval, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.restore_state import async_get_last_state _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['switch', 'sensor'] +DEPENDENCIES = ["switch", "sensor"] DEFAULT_TOLERANCE = 0.3 -DEFAULT_NAME = 'Generic Thermostat' +DEFAULT_NAME = "Generic Thermostat" -CONF_HEATER = 'heater' -CONF_SENSOR = 'target_sensor' -CONF_MIN_TEMP = 'min_temp' -CONF_MAX_TEMP = 'max_temp' -CONF_TARGET_TEMP = 'target_temp' -CONF_AC_MODE = 'ac_mode' -CONF_MIN_DUR = 'min_cycle_duration' -CONF_COLD_TOLERANCE = 'cold_tolerance' -CONF_HOT_TOLERANCE = 'hot_tolerance' -CONF_KEEP_ALIVE = 'keep_alive' -CONF_INITIAL_OPERATION_MODE = 'initial_operation_mode' -CONF_AWAY_TEMP = 'away_temp' -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | - SUPPORT_OPERATION_MODE) +CONF_HEATER = "heater" +CONF_SENSOR = "target_sensor" +CONF_MIN_TEMP = "min_temp" +CONF_MAX_TEMP = "max_temp" +CONF_TARGET_TEMP = "target_temp" +CONF_AC_MODE = "ac_mode" +CONF_MIN_DUR = "min_cycle_duration" +CONF_COLD_TOLERANCE = "cold_tolerance" +CONF_HOT_TOLERANCE = "hot_tolerance" +CONF_KEEP_ALIVE = "keep_alive" +CONF_INITIAL_OPERATION_MODE = "initial_operation_mode" +CONF_AWAY_TEMP = "away_temp" +SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HEATER): cv.entity_id, - vol.Required(CONF_SENSOR): cv.entity_id, - vol.Optional(CONF_AC_MODE): cv.boolean, - vol.Optional(CONF_MAX_TEMP): vol.Coerce(float), - vol.Optional(CONF_MIN_DUR): vol.All(cv.time_period, cv.positive_timedelta), - vol.Optional(CONF_MIN_TEMP): vol.Coerce(float), - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_COLD_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce( - float), - vol.Optional(CONF_HOT_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce( - float), - vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float), - vol.Optional(CONF_KEEP_ALIVE): vol.All( - cv.time_period, cv.positive_timedelta), - vol.Optional(CONF_INITIAL_OPERATION_MODE): - vol.In([STATE_AUTO, STATE_OFF]), - vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HEATER): cv.entity_id, + vol.Required(CONF_SENSOR): cv.entity_id, + vol.Optional(CONF_AC_MODE): cv.boolean, + vol.Optional(CONF_MAX_TEMP): vol.Coerce(float), + vol.Optional(CONF_MIN_DUR): vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_MIN_TEMP): vol.Coerce(float), + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_COLD_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(float), + vol.Optional(CONF_HOT_TOLERANCE, default=DEFAULT_TOLERANCE): vol.Coerce(float), + vol.Optional(CONF_TARGET_TEMP): vol.Coerce(float), + vol.Optional(CONF_KEEP_ALIVE): vol.All(cv.time_period, cv.positive_timedelta), + vol.Optional(CONF_INITIAL_OPERATION_MODE): vol.In([STATE_AUTO, STATE_OFF]), + vol.Optional(CONF_AWAY_TEMP): vol.Coerce(float), + } +) @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 generic thermostat platform.""" name = config.get(CONF_NAME) heater_entity_id = config.get(CONF_HEATER) @@ -85,19 +99,48 @@ def async_setup_platform(hass, config, async_add_entities, initial_operation_mode = config.get(CONF_INITIAL_OPERATION_MODE) away_temp = config.get(CONF_AWAY_TEMP) - async_add_entities([GenericThermostat( - hass, name, heater_entity_id, sensor_entity_id, min_temp, max_temp, - target_temp, ac_mode, min_cycle_duration, cold_tolerance, - hot_tolerance, keep_alive, initial_operation_mode, away_temp)]) + async_add_entities( + [ + GenericThermostat( + hass, + name, + heater_entity_id, + sensor_entity_id, + min_temp, + max_temp, + target_temp, + ac_mode, + min_cycle_duration, + cold_tolerance, + hot_tolerance, + keep_alive, + initial_operation_mode, + away_temp, + ) + ] + ) class GenericThermostat(ClimateDevice): """Representation of a Generic Thermostat device.""" - def __init__(self, hass, name, heater_entity_id, sensor_entity_id, - min_temp, max_temp, target_temp, ac_mode, min_cycle_duration, - cold_tolerance, hot_tolerance, keep_alive, - initial_operation_mode, away_temp): + def __init__( + self, + hass, + name, + heater_entity_id, + sensor_entity_id, + min_temp, + max_temp, + target_temp, + ac_mode, + min_cycle_duration, + cold_tolerance, + hot_tolerance, + keep_alive, + initial_operation_mode, + away_temp, + ): """Initialize the thermostat.""" self.hass = hass self._name = name @@ -108,8 +151,7 @@ class GenericThermostat(ClimateDevice): self._hot_tolerance = hot_tolerance self._keep_alive = keep_alive self._initial_operation_mode = initial_operation_mode - self._saved_target_temp = target_temp if target_temp is not None \ - else away_temp + self._saved_target_temp = target_temp if target_temp is not None else away_temp if self.ac_mode: self._current_operation = STATE_COOL self._operation_list = [STATE_COOL, STATE_OFF] @@ -134,14 +176,13 @@ class GenericThermostat(ClimateDevice): self._away_temp = away_temp self._is_away = False - async_track_state_change( - hass, sensor_entity_id, self._async_sensor_changed) - async_track_state_change( - hass, heater_entity_id, self._async_switch_changed) + async_track_state_change(hass, sensor_entity_id, self._async_sensor_changed) + async_track_state_change(hass, heater_entity_id, self._async_switch_changed) if self._keep_alive: async_track_time_interval( - hass, self._async_control_heating, self._keep_alive) + hass, self._async_control_heating, self._keep_alive + ) sensor_state = hass.states.get(sensor_entity_id) if sensor_state and sensor_state.state != STATE_UNKNOWN: @@ -151,8 +192,7 @@ class GenericThermostat(ClimateDevice): def async_added_to_hass(self): """Run when entity about to be added.""" # Check If we have an old state - old_state = yield from async_get_last_state(self.hass, - self.entity_id) + old_state = yield from async_get_last_state(self.hass, self.entity_id) if old_state is not None: # If we have no initial temperature, restore if self._target_temp is None: @@ -162,18 +202,19 @@ class GenericThermostat(ClimateDevice): self._target_temp = self.max_temp else: self._target_temp = self.min_temp - _LOGGER.warning("Undefined target temperature," - "falling back to %s", self._target_temp) + _LOGGER.warning( + "Undefined target temperature," "falling back to %s", + self._target_temp, + ) else: - self._target_temp = float( - old_state.attributes[ATTR_TEMPERATURE]) + self._target_temp = float(old_state.attributes[ATTR_TEMPERATURE]) if old_state.attributes.get(ATTR_AWAY_MODE) is not None: - self._is_away = str( - old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON - if (self._initial_operation_mode is None and - old_state.attributes[ATTR_OPERATION_MODE] is not None): - self._current_operation = \ - old_state.attributes[ATTR_OPERATION_MODE] + self._is_away = str(old_state.attributes[ATTR_AWAY_MODE]) == STATE_ON + if ( + self._initial_operation_mode is None + and old_state.attributes[ATTR_OPERATION_MODE] is not None + ): + self._current_operation = old_state.attributes[ATTR_OPERATION_MODE] self._enabled = self._current_operation != STATE_OFF else: @@ -183,8 +224,9 @@ class GenericThermostat(ClimateDevice): self._target_temp = self.max_temp else: self._target_temp = self.min_temp - _LOGGER.warning("No previously saved temperature, setting to %s", - self._target_temp) + _LOGGER.warning( + "No previously saved temperature, setting to %s", self._target_temp + ) @property def state(self): @@ -313,12 +355,14 @@ class GenericThermostat(ClimateDevice): async def _async_control_heating(self, time=None): """Check if we need to turn heating on or off.""" async with self._temp_lock: - if not self._active and None not in (self._cur_temp, - self._target_temp): + if not self._active and None not in (self._cur_temp, self._target_temp): self._active = True - _LOGGER.info("Obtained current and target temperature. " - "Generic thermostat active. %s, %s", - self._cur_temp, self._target_temp) + _LOGGER.info( + "Obtained current and target temperature. " + "Generic thermostat active. %s, %s", + self._cur_temp, + self._target_temp, + ) if not self._active or not self._enabled: return @@ -329,27 +373,25 @@ class GenericThermostat(ClimateDevice): else: current_state = STATE_OFF long_enough = condition.state( - self.hass, self.heater_entity_id, current_state, - self.min_cycle_duration) + self.hass, + self.heater_entity_id, + current_state, + self.min_cycle_duration, + ) if not long_enough: return - too_cold = \ - self._target_temp - self._cur_temp >= self._cold_tolerance - too_hot = \ - self._cur_temp - self._target_temp >= self._hot_tolerance + too_cold = self._target_temp - self._cur_temp >= self._cold_tolerance + too_hot = self._cur_temp - self._target_temp >= self._hot_tolerance if self._is_device_active: - if (self.ac_mode and too_cold) or \ - (not self.ac_mode and too_hot): - _LOGGER.info("Turning off heater %s", - self.heater_entity_id) + if (self.ac_mode and too_cold) or (not self.ac_mode and too_hot): + _LOGGER.info("Turning off heater %s", self.heater_entity_id) await self._async_heater_turn_off() elif time is not None: # The time argument is passed only in keep-alive case await self._async_heater_turn_on() else: - if (self.ac_mode and too_hot) or \ - (not self.ac_mode and too_cold): + if (self.ac_mode and too_hot) or (not self.ac_mode and too_cold): _LOGGER.info("Turning on heater %s", self.heater_entity_id) await self._async_heater_turn_on() elif time is not None: diff --git a/homeassistant/components/climate/heatmiser.py b/homeassistant/components/climate/heatmiser.py index a03d1567e..e75a0c160 100644 --- a/homeassistant/components/climate/heatmiser.py +++ b/homeassistant/components/climate/heatmiser.py @@ -9,29 +9,37 @@ import logging import voluptuous as vol from homeassistant.components.climate import ( - ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE) + ClimateDevice, + PLATFORM_SCHEMA, + SUPPORT_TARGET_TEMPERATURE, +) from homeassistant.const import ( - TEMP_CELSIUS, ATTR_TEMPERATURE, CONF_PORT, CONF_NAME, CONF_ID) + TEMP_CELSIUS, + ATTR_TEMPERATURE, + CONF_PORT, + CONF_NAME, + CONF_ID, +) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['heatmiserV3==0.9.1'] +REQUIREMENTS = ["heatmiserV3==0.9.1"] _LOGGER = logging.getLogger(__name__) -CONF_IPADDRESS = 'ipaddress' -CONF_TSTATS = 'tstats' +CONF_IPADDRESS = "ipaddress" +CONF_TSTATS = "tstats" -TSTATS_SCHEMA = vol.Schema({ - vol.Required(CONF_ID): cv.string, - vol.Required(CONF_NAME): cv.string, -}) +TSTATS_SCHEMA = vol.Schema( + {vol.Required(CONF_ID): cv.string, vol.Required(CONF_NAME): cv.string} +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_IPADDRESS): cv.string, - vol.Required(CONF_PORT): cv.port, - vol.Required(CONF_TSTATS, default={}): - vol.Schema({cv.string: TSTATS_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_IPADDRESS): cv.string, + vol.Required(CONF_PORT): cv.port, + vol.Required(CONF_TSTATS, default={}): vol.Schema({cv.string: TSTATS_SCHEMA}), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -46,10 +54,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): serport.open() for tstat in tstats.values(): - add_entities([ - HeatmiserV3Thermostat( - heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport) - ]) + add_entities( + [ + HeatmiserV3Thermostat( + heatmiser, tstat.get(CONF_ID), tstat.get(CONF_NAME), serport + ) + ] + ) class HeatmiserV3Thermostat(ClimateDevice): @@ -64,7 +75,7 @@ class HeatmiserV3Thermostat(ClimateDevice): self._id = device self.dcb = None self.update() - self._target_temperature = int(self.dcb.get('roomset')) + self._target_temperature = int(self.dcb.get("roomset")) @property def supported_features(self): @@ -85,8 +96,8 @@ class HeatmiserV3Thermostat(ClimateDevice): def current_temperature(self): """Return the current temperature.""" if self.dcb is not None: - low = self.dcb.get('floortemplow ') - high = self.dcb.get('floortemphigh') + low = self.dcb.get("floortemplow ") + high = self.dcb.get("floortemphigh") temp = (high * 256 + low) / 10.0 self._current_temperature = temp else: @@ -103,14 +114,9 @@ class HeatmiserV3Thermostat(ClimateDevice): temperature = kwargs.get(ATTR_TEMPERATURE) if temperature is None: return - self.heatmiser.hmSendAddress( - self._id, - 18, - temperature, - 1, - self.serport) + self.heatmiser.hmSendAddress(self._id, 18, temperature, 1, self.serport) self._target_temperature = temperature def update(self): """Get the latest data.""" - self.dcb = self.heatmiser.hmReadAddress(self._id, 'prt', self.serport) + self.dcb = self.heatmiser.hmReadAddress(self._id, "prt", self.serport) diff --git a/homeassistant/components/climate/hive.py b/homeassistant/components/climate/hive.py index 37289d45c..76be2ea11 100644 --- a/homeassistant/components/climate/hive.py +++ b/homeassistant/components/climate/hive.py @@ -5,20 +5,33 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/climate.hive/ """ from homeassistant.components.climate import ( - ClimateDevice, STATE_AUTO, STATE_HEAT, STATE_OFF, STATE_ON, - SUPPORT_AUX_HEAT, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE) + ClimateDevice, + STATE_AUTO, + STATE_HEAT, + STATE_OFF, + STATE_ON, + SUPPORT_AUX_HEAT, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_OPERATION_MODE, +) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.components.hive import DATA_HIVE -DEPENDENCIES = ['hive'] -HIVE_TO_HASS_STATE = {'SCHEDULE': STATE_AUTO, 'MANUAL': STATE_HEAT, - 'ON': STATE_ON, 'OFF': STATE_OFF} -HASS_TO_HIVE_STATE = {STATE_AUTO: 'SCHEDULE', STATE_HEAT: 'MANUAL', - STATE_ON: 'ON', STATE_OFF: 'OFF'} +DEPENDENCIES = ["hive"] +HIVE_TO_HASS_STATE = { + "SCHEDULE": STATE_AUTO, + "MANUAL": STATE_HEAT, + "ON": STATE_ON, + "OFF": STATE_OFF, +} +HASS_TO_HIVE_STATE = { + STATE_AUTO: "SCHEDULE", + STATE_HEAT: "MANUAL", + STATE_ON: "ON", + STATE_OFF: "OFF", +} -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | - SUPPORT_OPERATION_MODE | - SUPPORT_AUX_HEAT) +SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_AUX_HEAT def setup_platform(hass, config, add_entities, discovery_info=None): @@ -42,8 +55,7 @@ class HiveClimateEntity(ClimateDevice): self.thermostat_node_id = hivedevice["Thermostat_NodeID"] self.session = hivesession self.attributes = {} - self.data_updatesource = '{}.{}'.format(self.device_type, - self.node_id) + self.data_updatesource = "{}.{}".format(self.device_type, self.node_id) if self.device_type == "Heating": self.modes = [STATE_AUTO, STATE_HEAT, STATE_OFF] @@ -59,7 +71,7 @@ class HiveClimateEntity(ClimateDevice): def handle_update(self, updatesource): """Handle the new update request.""" - if '{}.{}'.format(self.device_type, self.node_id) not in updatesource: + if "{}.{}".format(self.device_type, self.node_id) not in updatesource: self.schedule_update_ha_state() @property @@ -69,7 +81,7 @@ class HiveClimateEntity(ClimateDevice): if self.device_type == "Heating": friendly_name = "Heating" if self.node_name is not None: - friendly_name = '{} {}'.format(self.node_name, friendly_name) + friendly_name = "{} {}".format(self.node_name, friendly_name) elif self.device_type == "HotWater": friendly_name = "Hot Water" return friendly_name @@ -138,8 +150,9 @@ class HiveClimateEntity(ClimateDevice): new_temperature = kwargs.get(ATTR_TEMPERATURE) if new_temperature is not None: if self.device_type == "Heating": - self.session.heating.set_target_temperature(self.node_id, - new_temperature) + self.session.heating.set_target_temperature( + self.node_id, new_temperature + ) for entity in self.session.entities: entity.handle_update(self.data_updatesource) @@ -161,12 +174,11 @@ class HiveClimateEntity(ClimateDevice): curtemp = self.session.heating.current_temperature(self.node_id) curtemp = round(curtemp * 2) / 2 target_boost_temperature = curtemp + 0.5 - self.session.heating.turn_boost_on(self.node_id, - target_boost_time, - target_boost_temperature) + self.session.heating.turn_boost_on( + self.node_id, target_boost_time, target_boost_temperature + ) elif self.device_type == "HotWater": - self.session.hotwater.turn_boost_on(self.node_id, - target_boost_time) + self.session.hotwater.turn_boost_on(self.node_id, target_boost_time) for entity in self.session.entities: entity.handle_update(self.data_updatesource) diff --git a/homeassistant/components/climate/homekit_controller.py b/homeassistant/components/climate/homekit_controller.py index f720fb602..2674b472c 100644 --- a/homeassistant/components/climate/homekit_controller.py +++ b/homeassistant/components/climate/homekit_controller.py @@ -6,23 +6,23 @@ https://home-assistant.io/components/climate.homekit_controller/ """ import logging -from homeassistant.components.homekit_controller import ( - HomeKitEntity, KNOWN_ACCESSORIES) +from homeassistant.components.homekit_controller import HomeKitEntity, KNOWN_ACCESSORIES from homeassistant.components.climate import ( - ClimateDevice, STATE_HEAT, STATE_COOL, STATE_IDLE, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE) + ClimateDevice, + STATE_HEAT, + STATE_COOL, + STATE_IDLE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_OPERATION_MODE, +) from homeassistant.const import TEMP_CELSIUS, STATE_OFF, ATTR_TEMPERATURE -DEPENDENCIES = ['homekit_controller'] +DEPENDENCIES = ["homekit_controller"] _LOGGER = logging.getLogger(__name__) # Map of Homekit operation modes to hass modes -MODE_HOMEKIT_TO_HASS = { - 0: STATE_OFF, - 1: STATE_HEAT, - 2: STATE_COOL, -} +MODE_HOMEKIT_TO_HASS = {0: STATE_OFF, 1: STATE_HEAT, 2: STATE_COOL} # Map of hass operation modes to homekit modes MODE_HASS_TO_HOMEKIT = {v: k for k, v in MODE_HOMEKIT_TO_HASS.items()} @@ -31,7 +31,7 @@ MODE_HASS_TO_HOMEKIT = {v: k for k, v in MODE_HOMEKIT_TO_HASS.items()} def setup_platform(hass, config, add_entities, discovery_info=None): """Set up Homekit climate.""" if discovery_info is not None: - accessory = hass.data[KNOWN_ACCESSORIES][discovery_info['serial']] + accessory = hass.data[KNOWN_ACCESSORIES][discovery_info["serial"]] add_entities([HomeKitClimateDevice(accessory, discovery_info)], True) @@ -53,38 +53,42 @@ class HomeKitClimateDevice(HomeKitEntity, ClimateDevice): from homekit import CharacteristicsTypes as ctypes for characteristic in characteristics: - ctype = characteristic['type'] + ctype = characteristic["type"] if ctype == ctypes.HEATING_COOLING_CURRENT: - self._state = MODE_HOMEKIT_TO_HASS.get( - characteristic['value']) + self._state = MODE_HOMEKIT_TO_HASS.get(characteristic["value"]) if ctype == ctypes.HEATING_COOLING_TARGET: - self._chars['target_mode'] = characteristic['iid'] + self._chars["target_mode"] = characteristic["iid"] self._features |= SUPPORT_OPERATION_MODE - self._current_mode = MODE_HOMEKIT_TO_HASS.get( - characteristic['value']) - self._valid_modes = [MODE_HOMEKIT_TO_HASS.get( - mode) for mode in characteristic['valid-values']] + self._current_mode = MODE_HOMEKIT_TO_HASS.get(characteristic["value"]) + self._valid_modes = [ + MODE_HOMEKIT_TO_HASS.get(mode) + for mode in characteristic["valid-values"] + ] elif ctype == ctypes.TEMPERATURE_CURRENT: - self._current_temp = characteristic['value'] + self._current_temp = characteristic["value"] elif ctype == ctypes.TEMPERATURE_TARGET: - self._chars['target_temp'] = characteristic['iid'] + self._chars["target_temp"] = characteristic["iid"] self._features |= SUPPORT_TARGET_TEMPERATURE - self._target_temp = characteristic['value'] + self._target_temp = characteristic["value"] def set_temperature(self, **kwargs): """Set new target temperature.""" temp = kwargs.get(ATTR_TEMPERATURE) - characteristics = [{'aid': self._aid, - 'iid': self._chars['target_temp'], - 'value': temp}] + characteristics = [ + {"aid": self._aid, "iid": self._chars["target_temp"], "value": temp} + ] self.put_characteristics(characteristics) def set_operation_mode(self, operation_mode): """Set new target operation mode.""" - characteristics = [{'aid': self._aid, - 'iid': self._chars['target_mode'], - 'value': MODE_HASS_TO_HOMEKIT[operation_mode]}] + characteristics = [ + { + "aid": self._aid, + "iid": self._chars["target_mode"], + "value": MODE_HASS_TO_HOMEKIT[operation_mode], + } + ] self.put_characteristics(characteristics) @property diff --git a/homeassistant/components/climate/homematic.py b/homeassistant/components/climate/homematic.py index 5b741a87b..f5c08a277 100644 --- a/homeassistant/components/climate/homematic.py +++ b/homeassistant/components/climate/homematic.py @@ -7,41 +7,41 @@ https://home-assistant.io/components/climate.homematic/ import logging from homeassistant.components.climate import ( - STATE_AUTO, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - ClimateDevice) + STATE_AUTO, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + ClimateDevice, +) from homeassistant.components.homematic import ( - ATTR_DISCOVER_DEVICES, HM_ATTRIBUTE_SUPPORT, HMDevice) + ATTR_DISCOVER_DEVICES, + HM_ATTRIBUTE_SUPPORT, + HMDevice, +) from homeassistant.const import ATTR_TEMPERATURE, STATE_UNKNOWN, TEMP_CELSIUS -DEPENDENCIES = ['homematic'] +DEPENDENCIES = ["homematic"] _LOGGER = logging.getLogger(__name__) -STATE_MANUAL = 'manual' -STATE_BOOST = 'boost' -STATE_COMFORT = 'comfort' -STATE_LOWERING = 'lowering' +STATE_MANUAL = "manual" +STATE_BOOST = "boost" +STATE_COMFORT = "comfort" +STATE_LOWERING = "lowering" HM_STATE_MAP = { - 'AUTO_MODE': STATE_AUTO, - 'MANU_MODE': STATE_MANUAL, - 'BOOST_MODE': STATE_BOOST, - 'COMFORT_MODE': STATE_COMFORT, - 'LOWERING_MODE': STATE_LOWERING + "AUTO_MODE": STATE_AUTO, + "MANU_MODE": STATE_MANUAL, + "BOOST_MODE": STATE_BOOST, + "COMFORT_MODE": STATE_COMFORT, + "LOWERING_MODE": STATE_LOWERING, } -HM_TEMP_MAP = [ - 'ACTUAL_TEMPERATURE', - 'TEMPERATURE', -] +HM_TEMP_MAP = ["ACTUAL_TEMPERATURE", "TEMPERATURE"] -HM_HUMI_MAP = [ - 'ACTUAL_HUMIDITY', - 'HUMIDITY', -] +HM_HUMI_MAP = ["ACTUAL_HUMIDITY", "HUMIDITY"] -HM_CONTROL_MODE = 'CONTROL_MODE' -HM_IP_CONTROL_MODE = 'SET_POINT_MODE' +HM_CONTROL_MODE = "CONTROL_MODE" +HM_IP_CONTROL_MODE = "SET_POINT_MODE" SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE @@ -78,9 +78,9 @@ class HMThermostat(HMDevice, ClimateDevice): if HM_CONTROL_MODE not in self._data: return None - set_point_mode = self._data.get('SET_POINT_MODE', -1) - control_mode = self._data.get('CONTROL_MODE', -1) - boost_mode = self._data.get('BOOST_MODE', False) + set_point_mode = self._data.get("SET_POINT_MODE", -1) + control_mode = self._data.get("CONTROL_MODE", -1) + boost_mode = self._data.get("BOOST_MODE", False) # boost mode is active if boost_mode: @@ -159,8 +159,10 @@ class HMThermostat(HMDevice, ClimateDevice): self._state = next(iter(self._hmdevice.WRITENODE.keys())) self._data[self._state] = STATE_UNKNOWN - if HM_CONTROL_MODE in self._hmdevice.ATTRIBUTENODE or \ - HM_IP_CONTROL_MODE in self._hmdevice.ATTRIBUTENODE: + if ( + HM_CONTROL_MODE in self._hmdevice.ATTRIBUTENODE + or HM_IP_CONTROL_MODE in self._hmdevice.ATTRIBUTENODE + ): self._data[HM_CONTROL_MODE] = STATE_UNKNOWN for node in self._hmdevice.SENSORNODE.keys(): diff --git a/homeassistant/components/climate/homematicip_cloud.py b/homeassistant/components/climate/homematicip_cloud.py index 966cd95ad..78965a416 100644 --- a/homeassistant/components/climate/homematicip_cloud.py +++ b/homeassistant/components/climate/homematicip_cloud.py @@ -7,27 +7,29 @@ https://home-assistant.io/components/climate.homematicip_cloud/ import logging from homeassistant.components.climate import ( - ATTR_TEMPERATURE, STATE_AUTO, STATE_MANUAL, SUPPORT_TARGET_TEMPERATURE, - ClimateDevice) + ATTR_TEMPERATURE, + STATE_AUTO, + STATE_MANUAL, + SUPPORT_TARGET_TEMPERATURE, + ClimateDevice, +) from homeassistant.components.homematicip_cloud import ( - HMIPC_HAPID, HomematicipGenericDevice) + HMIPC_HAPID, + HomematicipGenericDevice, +) from homeassistant.components.homematicip_cloud import DOMAIN as HMIPC_DOMAIN from homeassistant.const import TEMP_CELSIUS _LOGGER = logging.getLogger(__name__) -STATE_BOOST = 'Boost' +STATE_BOOST = "Boost" -HA_STATE_TO_HMIP = { - STATE_AUTO: 'AUTOMATIC', - STATE_MANUAL: 'MANUAL', -} +HA_STATE_TO_HMIP = {STATE_AUTO: "AUTOMATIC", STATE_MANUAL: "MANUAL"} HMIP_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_HMIP.items()} -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 HomematicIP Cloud climate devices.""" pass @@ -51,7 +53,7 @@ class HomematicipHeatingGroup(HomematicipGenericDevice, ClimateDevice): def __init__(self, home, device): """Initialize heating group.""" - device.modelType = 'Group-Heating' + device.modelType = "Group-Heating" super().__init__(home, device) @property diff --git a/homeassistant/components/climate/honeywell.py b/homeassistant/components/climate/honeywell.py index 6d54695fa..b9be536f4 100644 --- a/homeassistant/components/climate/honeywell.py +++ b/homeassistant/components/climate/honeywell.py @@ -13,42 +13,59 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.climate import ( - ClimateDevice, PLATFORM_SCHEMA, ATTR_FAN_MODE, ATTR_FAN_LIST, - ATTR_OPERATION_MODE, ATTR_OPERATION_LIST, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_AWAY_MODE, SUPPORT_OPERATION_MODE) + ClimateDevice, + PLATFORM_SCHEMA, + ATTR_FAN_MODE, + ATTR_FAN_LIST, + ATTR_OPERATION_MODE, + ATTR_OPERATION_LIST, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_AWAY_MODE, + SUPPORT_OPERATION_MODE, +) from homeassistant.const import ( - CONF_PASSWORD, CONF_USERNAME, TEMP_CELSIUS, TEMP_FAHRENHEIT, - ATTR_TEMPERATURE, CONF_REGION) + CONF_PASSWORD, + CONF_USERNAME, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, + ATTR_TEMPERATURE, + CONF_REGION, +) -REQUIREMENTS = ['evohomeclient==0.2.5', 'somecomfort==0.5.2'] +REQUIREMENTS = ["evohomeclient==0.2.5", "somecomfort==0.5.2"] _LOGGER = logging.getLogger(__name__) -ATTR_FAN = 'fan' -ATTR_SYSTEM_MODE = 'system_mode' -ATTR_CURRENT_OPERATION = 'equipment_output_status' +ATTR_FAN = "fan" +ATTR_SYSTEM_MODE = "system_mode" +ATTR_CURRENT_OPERATION = "equipment_output_status" -CONF_AWAY_TEMPERATURE = 'away_temperature' -CONF_COOL_AWAY_TEMPERATURE = 'away_cool_temperature' -CONF_HEAT_AWAY_TEMPERATURE = 'away_heat_temperature' +CONF_AWAY_TEMPERATURE = "away_temperature" +CONF_COOL_AWAY_TEMPERATURE = "away_cool_temperature" +CONF_HEAT_AWAY_TEMPERATURE = "away_heat_temperature" DEFAULT_AWAY_TEMPERATURE = 16 DEFAULT_COOL_AWAY_TEMPERATURE = 30 DEFAULT_HEAT_AWAY_TEMPERATURE = 16 -DEFAULT_REGION = 'eu' -REGIONS = ['eu', 'us'] +DEFAULT_REGION = "eu" +REGIONS = ["eu", "us"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_AWAY_TEMPERATURE, - default=DEFAULT_AWAY_TEMPERATURE): vol.Coerce(float), - vol.Optional(CONF_COOL_AWAY_TEMPERATURE, - default=DEFAULT_COOL_AWAY_TEMPERATURE): vol.Coerce(float), - vol.Optional(CONF_HEAT_AWAY_TEMPERATURE, - default=DEFAULT_HEAT_AWAY_TEMPERATURE): vol.Coerce(float), - vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(REGIONS), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional( + CONF_AWAY_TEMPERATURE, default=DEFAULT_AWAY_TEMPERATURE + ): vol.Coerce(float), + vol.Optional( + CONF_COOL_AWAY_TEMPERATURE, default=DEFAULT_COOL_AWAY_TEMPERATURE + ): vol.Coerce(float), + vol.Optional( + CONF_HEAT_AWAY_TEMPERATURE, default=DEFAULT_HEAT_AWAY_TEMPERATURE + ): vol.Coerce(float), + vol.Optional(CONF_REGION, default=DEFAULT_REGION): vol.In(REGIONS), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -57,7 +74,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): password = config.get(CONF_PASSWORD) region = config.get(CONF_REGION) - if region == 'us': + if region == "us": return _setup_us(username, password, config, add_entities) return _setup_round(username, password, config, add_entities) @@ -74,12 +91,10 @@ def _setup_round(username, password, config, add_entities): zones = evo_api.temperatures(force_refresh=True) for i, zone in enumerate(zones): add_entities( - [RoundThermostat(evo_api, zone['id'], i == 0, away_temp)], - True + [RoundThermostat(evo_api, zone["id"], i == 0, away_temp)], True ) except socket.error: - _LOGGER.error( - "Connection error logging into the honeywell evohome web service") + _LOGGER.error("Connection error logging into the honeywell evohome web service") return False return True @@ -98,17 +113,24 @@ def _setup_us(username, password, config, add_entities): _LOGGER.error("Failed to initialize honeywell client: %s", str(ex)) return False - dev_id = config.get('thermostat') - loc_id = config.get('location') + dev_id = config.get("thermostat") + loc_id = config.get("location") cool_away_temp = config.get(CONF_COOL_AWAY_TEMPERATURE) heat_away_temp = config.get(CONF_HEAT_AWAY_TEMPERATURE) - add_entities([HoneywellUSThermostat(client, device, cool_away_temp, - heat_away_temp, username, password) - for location in client.locations_by_id.values() - for device in location.devices_by_id.values() - if ((not loc_id or location.locationid == loc_id) and - (not dev_id or device.deviceid == dev_id))]) + add_entities( + [ + HoneywellUSThermostat( + client, device, cool_away_temp, heat_away_temp, username, password + ) + for location in client.locations_by_id.values() + for device in location.devices_by_id.values() + if ( + (not loc_id or location.locationid == loc_id) + and (not dev_id or device.deviceid == dev_id) + ) + ] + ) return True @@ -120,7 +142,7 @@ class RoundThermostat(ClimateDevice): self.client = client self._current_temperature = None self._target_temperature = None - self._name = 'round connected' + self._name = "round connected" self._id = zone_id self._master = master self._is_dhw = False @@ -130,7 +152,7 @@ class RoundThermostat(ClimateDevice): @property def supported_features(self): """Return the list of supported features.""" - supported = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE) + supported = SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE if hasattr(self.client, ATTR_SYSTEM_MODE): supported |= SUPPORT_OPERATION_MODE return supported @@ -200,7 +222,7 @@ class RoundThermostat(ClimateDevice): # Only refresh if this is the "master" device, # others will pick up the cache for val in self.client.temperatures(force_refresh=self._master): - if val['id'] == self._id: + if val["id"] == self._id: data = val except KeyError: @@ -209,31 +231,32 @@ class RoundThermostat(ClimateDevice): return except StopIteration: - _LOGGER.error("Did not receive any temperature data from the " - "evohomeclient API") + _LOGGER.error( + "Did not receive any temperature data from the " "evohomeclient API" + ) return - self._current_temperature = data['temp'] - self._target_temperature = data['setpoint'] - if data['thermostat'] == 'DOMESTIC_HOT_WATER': - self._name = 'Hot Water' + self._current_temperature = data["temp"] + self._target_temperature = data["setpoint"] + if data["thermostat"] == "DOMESTIC_HOT_WATER": + self._name = "Hot Water" self._is_dhw = True else: - self._name = data['name'] + self._name = data["name"] self._is_dhw = False # The underlying library doesn't expose the thermostat's mode # but we can pull it out of the big dictionary of information. device = self.client.devices[self._id] - self.client.system_mode = device[ - 'thermostat']['changeableValues']['mode'] + self.client.system_mode = device["thermostat"]["changeableValues"]["mode"] class HoneywellUSThermostat(ClimateDevice): """Representation of a Honeywell US Thermostat.""" - def __init__(self, client, device, cool_away_temp, - heat_away_temp, username, password): + def __init__( + self, client, device, cool_away_temp, heat_away_temp, username, password + ): """Initialize the thermostat.""" self._client = client self._device = device @@ -246,7 +269,7 @@ class HoneywellUSThermostat(ClimateDevice): @property def supported_features(self): """Return the list of supported features.""" - supported = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE) + supported = SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE if hasattr(self._device, ATTR_SYSTEM_MODE): supported |= SUPPORT_OPERATION_MODE return supported @@ -264,8 +287,7 @@ class HoneywellUSThermostat(ClimateDevice): @property def temperature_unit(self): """Return the unit of measurement.""" - return (TEMP_CELSIUS if self._device.temperature_unit == 'C' - else TEMP_FAHRENHEIT) + return TEMP_CELSIUS if self._device.temperature_unit == "C" else TEMP_FAHRENHEIT @property def current_temperature(self): @@ -275,7 +297,7 @@ class HoneywellUSThermostat(ClimateDevice): @property def target_temperature(self): """Return the temperature we try to reach.""" - if self._device.system_mode == 'cool': + if self._device.system_mode == "cool": return self._device.setpoint_cool return self._device.setpoint_heat @@ -293,25 +315,24 @@ class HoneywellUSThermostat(ClimateDevice): if temperature is None: return import somecomfort + try: # Get current mode mode = self._device.system_mode # Set hold if this is not the case if getattr(self._device, "hold_{}".format(mode)) is False: # Get next period key - next_period_key = '{}NextPeriod'.format(mode.capitalize()) + next_period_key = "{}NextPeriod".format(mode.capitalize()) # Get next period raw value next_period = self._device.raw_ui_data.get(next_period_key) # Get next period time hour, minute = divmod(next_period * 15, 60) # Set hold time - setattr(self._device, - "hold_{}".format(mode), - datetime.time(hour, minute)) + setattr( + self._device, "hold_{}".format(mode), datetime.time(hour, minute) + ) # Set temperature - setattr(self._device, - "setpoint_{}".format(mode), - temperature) + setattr(self._device, "setpoint_{}".format(mode), temperature) except somecomfort.SomeComfortError: _LOGGER.error("Temperature %.1f out of range", temperature) @@ -319,8 +340,9 @@ class HoneywellUSThermostat(ClimateDevice): def device_state_attributes(self): """Return the device specific state attributes.""" import somecomfort + data = { - ATTR_FAN: (self.is_fan_on and 'running' or 'idle'), + ATTR_FAN: (self.is_fan_on and "running" or "idle"), ATTR_FAN_MODE: self._device.fan_mode, ATTR_OPERATION_MODE: self._device.system_mode, } @@ -342,36 +364,40 @@ class HoneywellUSThermostat(ClimateDevice): """ self._away = True import somecomfort + try: # Get current mode mode = self._device.system_mode except somecomfort.SomeComfortError: - _LOGGER.error('Can not get system mode') + _LOGGER.error("Can not get system mode") return try: # Set permanent hold - setattr(self._device, - "hold_{}".format(mode), - True) + setattr(self._device, "hold_{}".format(mode), True) # Set temperature - setattr(self._device, - "setpoint_{}".format(mode), - getattr(self, "_{}_away_temp".format(mode))) + setattr( + self._device, + "setpoint_{}".format(mode), + getattr(self, "_{}_away_temp".format(mode)), + ) except somecomfort.SomeComfortError: - _LOGGER.error('Temperature %.1f out of range', - getattr(self, "_{}_away_temp".format(mode))) + _LOGGER.error( + "Temperature %.1f out of range", + getattr(self, "_{}_away_temp".format(mode)), + ) def turn_away_mode_off(self): """Turn away off.""" self._away = False import somecomfort + try: # Disabling all hold modes self._device.hold_cool = False self._device.hold_heat = False except somecomfort.SomeComfortError: - _LOGGER.error('Can not stop hold mode') + _LOGGER.error("Can not stop hold mode") def set_operation_mode(self, operation_mode: str) -> None: """Set the system mode (Cool, Heat, etc).""" @@ -381,20 +407,23 @@ class HoneywellUSThermostat(ClimateDevice): def update(self): """Update the state.""" import somecomfort + retries = 3 while retries > 0: try: self._device.refresh() break - except (somecomfort.client.APIRateLimited, OSError, - requests.exceptions.ReadTimeout) as exp: + except ( + somecomfort.client.APIRateLimited, + OSError, + requests.exceptions.ReadTimeout, + ) as exp: retries -= 1 if retries == 0: raise exp if not self._retry(): raise exp - _LOGGER.error( - "SomeComfort update failed, Retrying - Error: %s", exp) + _LOGGER.error("SomeComfort update failed, Retrying - Error: %s", exp) def _retry(self): """Recreate a new somecomfort client. @@ -403,22 +432,22 @@ class HoneywellUSThermostat(ClimateDevice): will succeed, is to recreate a new somecomfort client. """ import somecomfort + try: - self._client = somecomfort.SomeComfort( - self._username, self._password) + self._client = somecomfort.SomeComfort(self._username, self._password) except somecomfort.AuthError: - _LOGGER.error("Failed to login to honeywell account %s", - self._username) + _LOGGER.error("Failed to login to honeywell account %s", self._username) return False except somecomfort.SomeComfortError as ex: - _LOGGER.error("Failed to initialize honeywell client: %s", - str(ex)) + _LOGGER.error("Failed to initialize honeywell client: %s", str(ex)) return False - devices = [device - for location in self._client.locations_by_id.values() - for device in location.devices_by_id.values() - if device.name == self._device.name] + devices = [ + device + for location in self._client.locations_by_id.values() + for device in location.devices_by_id.values() + if device.name == self._device.name + ] if len(devices) != 1: _LOGGER.error("Failed to find device %s", self._device.name) diff --git a/homeassistant/components/climate/knx.py b/homeassistant/components/climate/knx.py index 4eada3566..232f20401 100644 --- a/homeassistant/components/climate/knx.py +++ b/homeassistant/components/climate/knx.py @@ -8,60 +8,65 @@ https://home-assistant.io/components/climate.knx/ import voluptuous as vol from homeassistant.components.climate import ( - PLATFORM_SCHEMA, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - ClimateDevice) + PLATFORM_SCHEMA, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + ClimateDevice, +) from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX from homeassistant.const import ATTR_TEMPERATURE, CONF_NAME, TEMP_CELSIUS from homeassistant.core import callback import homeassistant.helpers.config_validation as cv -CONF_SETPOINT_SHIFT_ADDRESS = 'setpoint_shift_address' -CONF_SETPOINT_SHIFT_STATE_ADDRESS = 'setpoint_shift_state_address' -CONF_SETPOINT_SHIFT_STEP = 'setpoint_shift_step' -CONF_SETPOINT_SHIFT_MAX = 'setpoint_shift_max' -CONF_SETPOINT_SHIFT_MIN = 'setpoint_shift_min' -CONF_TEMPERATURE_ADDRESS = 'temperature_address' -CONF_TARGET_TEMPERATURE_ADDRESS = 'target_temperature_address' -CONF_OPERATION_MODE_ADDRESS = 'operation_mode_address' -CONF_OPERATION_MODE_STATE_ADDRESS = 'operation_mode_state_address' -CONF_CONTROLLER_STATUS_ADDRESS = 'controller_status_address' -CONF_CONTROLLER_STATUS_STATE_ADDRESS = 'controller_status_state_address' -CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS = \ - 'operation_mode_frost_protection_address' -CONF_OPERATION_MODE_NIGHT_ADDRESS = 'operation_mode_night_address' -CONF_OPERATION_MODE_COMFORT_ADDRESS = 'operation_mode_comfort_address' +CONF_SETPOINT_SHIFT_ADDRESS = "setpoint_shift_address" +CONF_SETPOINT_SHIFT_STATE_ADDRESS = "setpoint_shift_state_address" +CONF_SETPOINT_SHIFT_STEP = "setpoint_shift_step" +CONF_SETPOINT_SHIFT_MAX = "setpoint_shift_max" +CONF_SETPOINT_SHIFT_MIN = "setpoint_shift_min" +CONF_TEMPERATURE_ADDRESS = "temperature_address" +CONF_TARGET_TEMPERATURE_ADDRESS = "target_temperature_address" +CONF_OPERATION_MODE_ADDRESS = "operation_mode_address" +CONF_OPERATION_MODE_STATE_ADDRESS = "operation_mode_state_address" +CONF_CONTROLLER_STATUS_ADDRESS = "controller_status_address" +CONF_CONTROLLER_STATUS_STATE_ADDRESS = "controller_status_state_address" +CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS = "operation_mode_frost_protection_address" +CONF_OPERATION_MODE_NIGHT_ADDRESS = "operation_mode_night_address" +CONF_OPERATION_MODE_COMFORT_ADDRESS = "operation_mode_comfort_address" -DEFAULT_NAME = 'KNX Climate' +DEFAULT_NAME = "KNX Climate" DEFAULT_SETPOINT_SHIFT_STEP = 0.5 DEFAULT_SETPOINT_SHIFT_MAX = 6 DEFAULT_SETPOINT_SHIFT_MIN = -6 -DEPENDENCIES = ['knx'] +DEPENDENCIES = ["knx"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string, - vol.Required(CONF_TARGET_TEMPERATURE_ADDRESS): cv.string, - vol.Optional(CONF_SETPOINT_SHIFT_ADDRESS): cv.string, - vol.Optional(CONF_SETPOINT_SHIFT_STATE_ADDRESS): cv.string, - vol.Optional(CONF_SETPOINT_SHIFT_STEP, - default=DEFAULT_SETPOINT_SHIFT_STEP): vol.All( - float, vol.Range(min=0, max=2)), - vol.Optional(CONF_SETPOINT_SHIFT_MAX, default=DEFAULT_SETPOINT_SHIFT_MAX): - vol.All(int, vol.Range(min=0, max=32)), - vol.Optional(CONF_SETPOINT_SHIFT_MIN, default=DEFAULT_SETPOINT_SHIFT_MIN): - vol.All(int, vol.Range(min=-32, max=0)), - vol.Optional(CONF_OPERATION_MODE_ADDRESS): cv.string, - vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string, - vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string, - vol.Optional(CONF_CONTROLLER_STATUS_STATE_ADDRESS): cv.string, - vol.Optional(CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS): cv.string, - vol.Optional(CONF_OPERATION_MODE_NIGHT_ADDRESS): cv.string, - vol.Optional(CONF_OPERATION_MODE_COMFORT_ADDRESS): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_TEMPERATURE_ADDRESS): cv.string, + vol.Required(CONF_TARGET_TEMPERATURE_ADDRESS): cv.string, + vol.Optional(CONF_SETPOINT_SHIFT_ADDRESS): cv.string, + vol.Optional(CONF_SETPOINT_SHIFT_STATE_ADDRESS): cv.string, + vol.Optional( + CONF_SETPOINT_SHIFT_STEP, default=DEFAULT_SETPOINT_SHIFT_STEP + ): vol.All(float, vol.Range(min=0, max=2)), + vol.Optional( + CONF_SETPOINT_SHIFT_MAX, default=DEFAULT_SETPOINT_SHIFT_MAX + ): vol.All(int, vol.Range(min=0, max=32)), + vol.Optional( + CONF_SETPOINT_SHIFT_MIN, default=DEFAULT_SETPOINT_SHIFT_MIN + ): vol.All(int, vol.Range(min=-32, max=0)), + vol.Optional(CONF_OPERATION_MODE_ADDRESS): cv.string, + vol.Optional(CONF_OPERATION_MODE_STATE_ADDRESS): cv.string, + vol.Optional(CONF_CONTROLLER_STATUS_ADDRESS): cv.string, + vol.Optional(CONF_CONTROLLER_STATUS_STATE_ADDRESS): cv.string, + vol.Optional(CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS): cv.string, + vol.Optional(CONF_OPERATION_MODE_NIGHT_ADDRESS): cv.string, + vol.Optional(CONF_OPERATION_MODE_COMFORT_ADDRESS): 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 climate(s) for KNX platform.""" if discovery_info is not None: async_add_entities_discovery(hass, discovery_info, async_add_entities) @@ -88,27 +93,32 @@ def async_add_entities_config(hass, config, async_add_entities): hass.data[DATA_KNX].xknx, name=config.get(CONF_NAME), group_address_temperature=config.get(CONF_TEMPERATURE_ADDRESS), - group_address_target_temperature=config.get( - CONF_TARGET_TEMPERATURE_ADDRESS), + group_address_target_temperature=config.get(CONF_TARGET_TEMPERATURE_ADDRESS), group_address_setpoint_shift=config.get(CONF_SETPOINT_SHIFT_ADDRESS), group_address_setpoint_shift_state=config.get( - CONF_SETPOINT_SHIFT_STATE_ADDRESS), + CONF_SETPOINT_SHIFT_STATE_ADDRESS + ), setpoint_shift_step=config.get(CONF_SETPOINT_SHIFT_STEP), setpoint_shift_max=config.get(CONF_SETPOINT_SHIFT_MAX), setpoint_shift_min=config.get(CONF_SETPOINT_SHIFT_MIN), group_address_operation_mode=config.get(CONF_OPERATION_MODE_ADDRESS), group_address_operation_mode_state=config.get( - CONF_OPERATION_MODE_STATE_ADDRESS), - group_address_controller_status=config.get( - CONF_CONTROLLER_STATUS_ADDRESS), + CONF_OPERATION_MODE_STATE_ADDRESS + ), + group_address_controller_status=config.get(CONF_CONTROLLER_STATUS_ADDRESS), group_address_controller_status_state=config.get( - CONF_CONTROLLER_STATUS_STATE_ADDRESS), + CONF_CONTROLLER_STATUS_STATE_ADDRESS + ), group_address_operation_mode_protection=config.get( - CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS), + CONF_OPERATION_MODE_FROST_PROTECTION_ADDRESS + ), group_address_operation_mode_night=config.get( - CONF_OPERATION_MODE_NIGHT_ADDRESS), + CONF_OPERATION_MODE_NIGHT_ADDRESS + ), group_address_operation_mode_comfort=config.get( - CONF_OPERATION_MODE_COMFORT_ADDRESS)) + CONF_OPERATION_MODE_COMFORT_ADDRESS + ), + ) hass.data[DATA_KNX].xknx.devices.add(climate) async_add_entities([KNXClimate(hass, climate)]) @@ -132,9 +142,11 @@ class KNXClimate(ClimateDevice): def async_register_callbacks(self): """Register callbacks to update hass after device was changed.""" + async def after_update_callback(device): """Call after device was updated.""" await self.async_update_ha_state() + self.device.register_device_updated_cb(after_update_callback) @property @@ -200,13 +212,15 @@ class KNXClimate(ClimateDevice): @property def operation_list(self): """Return the list of available operation modes.""" - return [operation_mode.value for - operation_mode in - self.device.get_supported_operation_modes()] + return [ + operation_mode.value + for operation_mode in self.device.get_supported_operation_modes() + ] async def async_set_operation_mode(self, operation_mode): """Set operation mode.""" if self.device.supports_operation_mode: from xknx.knx import HVACOperationMode + knx_operation_mode = HVACOperationMode(operation_mode) await self.device.set_operation_mode(knx_operation_mode) diff --git a/homeassistant/components/climate/maxcube.py b/homeassistant/components/climate/maxcube.py index 328cdabde..6d7c65553 100644 --- a/homeassistant/components/climate/maxcube.py +++ b/homeassistant/components/climate/maxcube.py @@ -8,16 +8,19 @@ import socket import logging from homeassistant.components.climate import ( - ClimateDevice, STATE_AUTO, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_OPERATION_MODE) + ClimateDevice, + STATE_AUTO, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_OPERATION_MODE, +) from homeassistant.components.maxcube import DATA_KEY from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE _LOGGER = logging.getLogger(__name__) -STATE_MANUAL = 'manual' -STATE_BOOST = 'boost' -STATE_VACATION = 'vacation' +STATE_MANUAL = "manual" +STATE_BOOST = "boost" +STATE_VACATION = "vacation" SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE @@ -28,12 +31,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for handler in hass.data[DATA_KEY].values(): cube = handler.cube for device in cube.devices: - name = '{} {}'.format( - cube.room_by_id(device.room_id).name, device.name) + name = "{} {}".format(cube.room_by_id(device.room_id).name, device.name) if cube.is_thermostat(device) or cube.is_wallthermostat(device): - devices.append( - MaxCubeClimate(handler, name, device.rf_address)) + devices.append(MaxCubeClimate(handler, name, device.rf_address)) if devices: add_entities(devices) @@ -45,8 +46,7 @@ class MaxCubeClimate(ClimateDevice): def __init__(self, handler, name, rf_address): """Initialize MAX! Cube ClimateDevice.""" self._name = name - self._operation_list = [STATE_AUTO, STATE_MANUAL, STATE_BOOST, - STATE_VACATION] + self._operation_list = [STATE_AUTO, STATE_MANUAL, STATE_BOOST, STATE_VACATION] self._rf_address = rf_address self._cubehandle = handler @@ -154,11 +154,12 @@ class MaxCubeClimate(ClimateDevice): @staticmethod def map_mode_hass_max(operation_mode): """Map Home Assistant Operation Modes to MAX! Operation Modes.""" - from maxcube.device import \ - MAX_DEVICE_MODE_AUTOMATIC, \ - MAX_DEVICE_MODE_MANUAL, \ - MAX_DEVICE_MODE_VACATION, \ - MAX_DEVICE_MODE_BOOST + from maxcube.device import ( + MAX_DEVICE_MODE_AUTOMATIC, + MAX_DEVICE_MODE_MANUAL, + MAX_DEVICE_MODE_VACATION, + MAX_DEVICE_MODE_BOOST, + ) if operation_mode == STATE_AUTO: mode = MAX_DEVICE_MODE_AUTOMATIC @@ -176,11 +177,12 @@ class MaxCubeClimate(ClimateDevice): @staticmethod def map_mode_max_hass(mode): """Map MAX! Operation Modes to Home Assistant Operation Modes.""" - from maxcube.device import \ - MAX_DEVICE_MODE_AUTOMATIC, \ - MAX_DEVICE_MODE_MANUAL, \ - MAX_DEVICE_MODE_VACATION, \ - MAX_DEVICE_MODE_BOOST + from maxcube.device import ( + MAX_DEVICE_MODE_AUTOMATIC, + MAX_DEVICE_MODE_MANUAL, + MAX_DEVICE_MODE_VACATION, + MAX_DEVICE_MODE_BOOST, + ) if mode == MAX_DEVICE_MODE_AUTOMATIC: operation_mode = STATE_AUTO diff --git a/homeassistant/components/climate/melissa.py b/homeassistant/components/climate/melissa.py index c8e67c148..7cd7e1a93 100644 --- a/homeassistant/components/climate/melissa.py +++ b/homeassistant/components/climate/melissa.py @@ -7,31 +7,42 @@ https://home-assistant.io/components/climate.melissa/ import logging from homeassistant.components.climate import ( - ClimateDevice, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_ON_OFF, STATE_AUTO, STATE_HEAT, STATE_COOL, STATE_DRY, - STATE_FAN_ONLY, SUPPORT_FAN_MODE + ClimateDevice, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_ON_OFF, + STATE_AUTO, + STATE_HEAT, + STATE_COOL, + STATE_DRY, + STATE_FAN_ONLY, + SUPPORT_FAN_MODE, ) from homeassistant.components.fan import SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH from homeassistant.components.melissa import DATA_MELISSA from homeassistant.const import ( - TEMP_CELSIUS, STATE_ON, STATE_OFF, STATE_IDLE, ATTR_TEMPERATURE, - PRECISION_WHOLE + TEMP_CELSIUS, + STATE_ON, + STATE_OFF, + STATE_IDLE, + ATTR_TEMPERATURE, + PRECISION_WHOLE, ) -DEPENDENCIES = ['melissa'] +DEPENDENCIES = ["melissa"] _LOGGER = logging.getLogger(__name__) -SUPPORT_FLAGS = (SUPPORT_FAN_MODE | SUPPORT_OPERATION_MODE | - SUPPORT_ON_OFF | SUPPORT_TARGET_TEMPERATURE) +SUPPORT_FLAGS = ( + SUPPORT_FAN_MODE + | SUPPORT_OPERATION_MODE + | SUPPORT_ON_OFF + | SUPPORT_TARGET_TEMPERATURE +) -OP_MODES = [ - STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT -] +OP_MODES = [STATE_COOL, STATE_DRY, STATE_FAN_ONLY, STATE_HEAT] -FAN_MODES = [ - STATE_AUTO, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM -] +FAN_MODES = [STATE_AUTO, SPEED_HIGH, SPEED_LOW, SPEED_MEDIUM] def setup_platform(hass, config, add_entities, discovery_info=None): @@ -42,9 +53,8 @@ def setup_platform(hass, config, add_entities, discovery_info=None): all_devices = [] for device in devices: - if device['type'] == 'melissa': - all_devices.append(MelissaClimate( - api, device['serial_number'], device)) + if device["type"] == "melissa": + all_devices.append(MelissaClimate(api, device["serial_number"], device)) add_entities(all_devices) @@ -54,10 +64,10 @@ class MelissaClimate(ClimateDevice): def __init__(self, api, serial_number, init_data): """Initialize the climate device.""" - self._name = init_data['name'] + self._name = init_data["name"] self._api = api self._serial_number = serial_number - self._data = init_data['controller_log'] + self._data = init_data["controller_log"] self._state = None self._cur_settings = None @@ -71,15 +81,16 @@ class MelissaClimate(ClimateDevice): """Return current state.""" if self._cur_settings is not None: return self._cur_settings[self._api.STATE] in ( - self._api.STATE_ON, self._api.STATE_IDLE) + self._api.STATE_ON, + self._api.STATE_IDLE, + ) return None @property def current_fan_mode(self): """Return the current fan mode.""" if self._cur_settings is not None: - return self.melissa_fan_to_hass( - self._cur_settings[self._api.FAN]) + return self.melissa_fan_to_hass(self._cur_settings[self._api.FAN]) @property def current_temperature(self): @@ -96,8 +107,7 @@ class MelissaClimate(ClimateDevice): def current_operation(self): """Return the current operation mode.""" if self._cur_settings is not None: - return self.melissa_op_to_hass( - self._cur_settings[self._api.MODE]) + return self.melissa_op_to_hass(self._cur_settings[self._api.MODE]) @property def operation_list(self): @@ -119,8 +129,7 @@ class MelissaClimate(ClimateDevice): def state(self): """Return current state.""" if self._cur_settings is not None: - return self.melissa_state_to_hass( - self._cur_settings[self._api.STATE]) + return self.melissa_state_to_hass(self._cur_settings[self._api.STATE]) @property def temperature_unit(self): @@ -181,12 +190,11 @@ class MelissaClimate(ClimateDevice): """Get latest data from Melissa.""" try: self._data = self._api.status(cached=True)[self._serial_number] - self._cur_settings = self._api.cur_settings( - self._serial_number - )['controller']['_relation']['command_log'] + self._cur_settings = self._api.cur_settings(self._serial_number)[ + "controller" + ]["_relation"]["command_log"] except KeyError: - _LOGGER.warning( - 'Unable to update entity %s', self.entity_id) + _LOGGER.warning("Unable to update entity %s", self.entity_id) def melissa_state_to_hass(self, state): """Translate Melissa states to hass states.""" @@ -208,8 +216,7 @@ class MelissaClimate(ClimateDevice): return STATE_DRY if mode == self._api.MODE_FAN: return STATE_FAN_ONLY - _LOGGER.warning( - "Operation mode %s could not be mapped to hass", mode) + _LOGGER.warning("Operation mode %s could not be mapped to hass", mode) return None def melissa_fan_to_hass(self, fan): diff --git a/homeassistant/components/climate/modbus.py b/homeassistant/components/climate/modbus.py index 1c5c03e45..a402c5d66 100644 --- a/homeassistant/components/climate/modbus.py +++ b/homeassistant/components/climate/modbus.py @@ -13,37 +13,42 @@ import struct import voluptuous as vol -from homeassistant.const import ( - CONF_NAME, CONF_SLAVE, ATTR_TEMPERATURE) +from homeassistant.const import CONF_NAME, CONF_SLAVE, ATTR_TEMPERATURE from homeassistant.components.climate import ( - ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE) + ClimateDevice, + PLATFORM_SCHEMA, + SUPPORT_TARGET_TEMPERATURE, +) from homeassistant.components import modbus import homeassistant.helpers.config_validation as cv -DEPENDENCIES = ['modbus'] +DEPENDENCIES = ["modbus"] # Parameters not defined by homeassistant.const -CONF_TARGET_TEMP = 'target_temp_register' -CONF_CURRENT_TEMP = 'current_temp_register' -CONF_DATA_TYPE = 'data_type' -CONF_COUNT = 'data_count' -CONF_PRECISION = 'precision' +CONF_TARGET_TEMP = "target_temp_register" +CONF_CURRENT_TEMP = "current_temp_register" +CONF_DATA_TYPE = "data_type" +CONF_COUNT = "data_count" +CONF_PRECISION = "precision" -DATA_TYPE_INT = 'int' -DATA_TYPE_UINT = 'uint' -DATA_TYPE_FLOAT = 'float' +DATA_TYPE_INT = "int" +DATA_TYPE_UINT = "uint" +DATA_TYPE_FLOAT = "float" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_NAME): cv.string, - vol.Required(CONF_SLAVE): cv.positive_int, - vol.Required(CONF_TARGET_TEMP): cv.positive_int, - vol.Required(CONF_CURRENT_TEMP): cv.positive_int, - vol.Optional(CONF_DATA_TYPE, default=DATA_TYPE_FLOAT): - vol.In([DATA_TYPE_INT, DATA_TYPE_UINT, DATA_TYPE_FLOAT]), - vol.Optional(CONF_COUNT, default=2): cv.positive_int, - vol.Optional(CONF_PRECISION, default=1): cv.positive_int -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_NAME): cv.string, + vol.Required(CONF_SLAVE): cv.positive_int, + vol.Required(CONF_TARGET_TEMP): cv.positive_int, + vol.Required(CONF_CURRENT_TEMP): cv.positive_int, + vol.Optional(CONF_DATA_TYPE, default=DATA_TYPE_FLOAT): vol.In( + [DATA_TYPE_INT, DATA_TYPE_UINT, DATA_TYPE_FLOAT] + ), + vol.Optional(CONF_COUNT, default=2): cv.positive_int, + vol.Optional(CONF_PRECISION, default=1): cv.positive_int, + } +) _LOGGER = logging.getLogger(__name__) @@ -60,16 +65,35 @@ def setup_platform(hass, config, add_entities, discovery_info=None): count = config.get(CONF_COUNT) precision = config.get(CONF_PRECISION) - add_entities([ModbusThermostat(name, modbus_slave, - target_temp_register, current_temp_register, - data_type, count, precision)], True) + add_entities( + [ + ModbusThermostat( + name, + modbus_slave, + target_temp_register, + current_temp_register, + data_type, + count, + precision, + ) + ], + True, + ) class ModbusThermostat(ClimateDevice): """Representation of a Modbus Thermostat.""" - def __init__(self, name, modbus_slave, target_temp_register, - current_temp_register, data_type, count, precision): + def __init__( + self, + name, + modbus_slave, + target_temp_register, + current_temp_register, + data_type, + count, + precision, + ): """Initialize the unit.""" self._name = name self._slave = modbus_slave @@ -80,14 +104,15 @@ class ModbusThermostat(ClimateDevice): self._data_type = data_type self._count = int(count) self._precision = precision - self._structure = '>f' + self._structure = ">f" - data_types = {DATA_TYPE_INT: {1: 'h', 2: 'i', 4: 'q'}, - DATA_TYPE_UINT: {1: 'H', 2: 'I', 4: 'Q'}, - DATA_TYPE_FLOAT: {1: 'e', 2: 'f', 4: 'd'}} + data_types = { + DATA_TYPE_INT: {1: "h", 2: "i", 4: "q"}, + DATA_TYPE_UINT: {1: "H", 2: "I", 4: "Q"}, + DATA_TYPE_FLOAT: {1: "e", 2: "f", 4: "d"}, + } - self._structure = '>{}'.format(data_types[self._data_type] - [self._count]) + self._structure = ">{}".format(data_types[self._data_type][self._count]) @property def supported_features(self): @@ -96,10 +121,10 @@ class ModbusThermostat(ClimateDevice): def update(self): """Update Target & Current Temperature.""" - self._target_temperature = self.read_register( - self._target_temperature_register) + self._target_temperature = self.read_register(self._target_temperature_register) self._current_temperature = self.read_register( - self._current_temperature_register) + self._current_temperature_register + ) @property def name(self): @@ -122,25 +147,26 @@ class ModbusThermostat(ClimateDevice): if target_temperature is None: return byte_string = struct.pack(self._structure, target_temperature) - register_value = struct.unpack('>h', byte_string[0:2])[0] + register_value = struct.unpack(">h", byte_string[0:2])[0] try: - self.write_register(self._target_temperature_register, - register_value) + self.write_register(self._target_temperature_register, register_value) except AttributeError as ex: _LOGGER.error(ex) def read_register(self, register): """Read holding register using the modbus hub slave.""" try: - result = modbus.HUB.read_holding_registers(self._slave, register, - self._count) + result = modbus.HUB.read_holding_registers( + self._slave, register, self._count + ) except AttributeError as ex: _LOGGER.error(ex) - byte_string = b''.join( - [x.to_bytes(2, byteorder='big') for x in result.registers]) + byte_string = b"".join( + [x.to_bytes(2, byteorder="big") for x in result.registers] + ) val = struct.unpack(self._structure, byte_string)[0] - register_value = format(val, '.{}f'.format(self._precision)) + register_value = format(val, ".{}f".format(self._precision)) return register_value def write_register(self, register, value): diff --git a/homeassistant/components/climate/mqtt.py b/homeassistant/components/climate/mqtt.py index 9e227e002..52eef67db 100644 --- a/homeassistant/components/climate/mqtt.py +++ b/homeassistant/components/climate/mqtt.py @@ -13,122 +13,150 @@ from homeassistant.core import callback from homeassistant.components import mqtt from homeassistant.components.climate import ( - STATE_HEAT, STATE_COOL, STATE_DRY, STATE_FAN_ONLY, ClimateDevice, - PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, STATE_AUTO, - ATTR_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, - SUPPORT_SWING_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE, SUPPORT_HOLD_MODE, - SUPPORT_AUX_HEAT, DEFAULT_MIN_TEMP, DEFAULT_MAX_TEMP) + STATE_HEAT, + STATE_COOL, + STATE_DRY, + STATE_FAN_ONLY, + ClimateDevice, + PLATFORM_SCHEMA as CLIMATE_PLATFORM_SCHEMA, + STATE_AUTO, + ATTR_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_OPERATION_MODE, + SUPPORT_SWING_MODE, + SUPPORT_FAN_MODE, + SUPPORT_AWAY_MODE, + SUPPORT_HOLD_MODE, + SUPPORT_AUX_HEAT, + DEFAULT_MIN_TEMP, + DEFAULT_MAX_TEMP, +) from homeassistant.const import ( - STATE_ON, STATE_OFF, ATTR_TEMPERATURE, CONF_NAME, CONF_VALUE_TEMPLATE) + STATE_ON, + STATE_OFF, + ATTR_TEMPERATURE, + CONF_NAME, + CONF_VALUE_TEMPLATE, +) from homeassistant.components.mqtt import ( - CONF_AVAILABILITY_TOPIC, CONF_QOS, CONF_RETAIN, CONF_PAYLOAD_AVAILABLE, - CONF_PAYLOAD_NOT_AVAILABLE, MQTT_BASE_PLATFORM_SCHEMA, MqttAvailability) + CONF_AVAILABILITY_TOPIC, + CONF_QOS, + CONF_RETAIN, + CONF_PAYLOAD_AVAILABLE, + CONF_PAYLOAD_NOT_AVAILABLE, + MQTT_BASE_PLATFORM_SCHEMA, + MqttAvailability, +) import homeassistant.helpers.config_validation as cv -from homeassistant.components.fan import (SPEED_LOW, SPEED_MEDIUM, - SPEED_HIGH) +from homeassistant.components.fan import SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['mqtt'] +DEPENDENCIES = ["mqtt"] -DEFAULT_NAME = 'MQTT HVAC' +DEFAULT_NAME = "MQTT HVAC" -CONF_POWER_COMMAND_TOPIC = 'power_command_topic' -CONF_POWER_STATE_TOPIC = 'power_state_topic' -CONF_POWER_STATE_TEMPLATE = 'power_state_template' -CONF_MODE_COMMAND_TOPIC = 'mode_command_topic' -CONF_MODE_STATE_TOPIC = 'mode_state_topic' -CONF_MODE_STATE_TEMPLATE = 'mode_state_template' -CONF_TEMPERATURE_COMMAND_TOPIC = 'temperature_command_topic' -CONF_TEMPERATURE_STATE_TOPIC = 'temperature_state_topic' -CONF_TEMPERATURE_STATE_TEMPLATE = 'temperature_state_template' -CONF_FAN_MODE_COMMAND_TOPIC = 'fan_mode_command_topic' -CONF_FAN_MODE_STATE_TOPIC = 'fan_mode_state_topic' -CONF_FAN_MODE_STATE_TEMPLATE = 'fan_mode_state_template' -CONF_SWING_MODE_COMMAND_TOPIC = 'swing_mode_command_topic' -CONF_SWING_MODE_STATE_TOPIC = 'swing_mode_state_topic' -CONF_SWING_MODE_STATE_TEMPLATE = 'swing_mode_state_template' -CONF_AWAY_MODE_COMMAND_TOPIC = 'away_mode_command_topic' -CONF_AWAY_MODE_STATE_TOPIC = 'away_mode_state_topic' -CONF_AWAY_MODE_STATE_TEMPLATE = 'away_mode_state_template' -CONF_HOLD_COMMAND_TOPIC = 'hold_command_topic' -CONF_HOLD_STATE_TOPIC = 'hold_state_topic' -CONF_HOLD_STATE_TEMPLATE = 'hold_state_template' -CONF_AUX_COMMAND_TOPIC = 'aux_command_topic' -CONF_AUX_STATE_TOPIC = 'aux_state_topic' -CONF_AUX_STATE_TEMPLATE = 'aux_state_template' +CONF_POWER_COMMAND_TOPIC = "power_command_topic" +CONF_POWER_STATE_TOPIC = "power_state_topic" +CONF_POWER_STATE_TEMPLATE = "power_state_template" +CONF_MODE_COMMAND_TOPIC = "mode_command_topic" +CONF_MODE_STATE_TOPIC = "mode_state_topic" +CONF_MODE_STATE_TEMPLATE = "mode_state_template" +CONF_TEMPERATURE_COMMAND_TOPIC = "temperature_command_topic" +CONF_TEMPERATURE_STATE_TOPIC = "temperature_state_topic" +CONF_TEMPERATURE_STATE_TEMPLATE = "temperature_state_template" +CONF_FAN_MODE_COMMAND_TOPIC = "fan_mode_command_topic" +CONF_FAN_MODE_STATE_TOPIC = "fan_mode_state_topic" +CONF_FAN_MODE_STATE_TEMPLATE = "fan_mode_state_template" +CONF_SWING_MODE_COMMAND_TOPIC = "swing_mode_command_topic" +CONF_SWING_MODE_STATE_TOPIC = "swing_mode_state_topic" +CONF_SWING_MODE_STATE_TEMPLATE = "swing_mode_state_template" +CONF_AWAY_MODE_COMMAND_TOPIC = "away_mode_command_topic" +CONF_AWAY_MODE_STATE_TOPIC = "away_mode_state_topic" +CONF_AWAY_MODE_STATE_TEMPLATE = "away_mode_state_template" +CONF_HOLD_COMMAND_TOPIC = "hold_command_topic" +CONF_HOLD_STATE_TOPIC = "hold_state_topic" +CONF_HOLD_STATE_TEMPLATE = "hold_state_template" +CONF_AUX_COMMAND_TOPIC = "aux_command_topic" +CONF_AUX_STATE_TOPIC = "aux_state_topic" +CONF_AUX_STATE_TEMPLATE = "aux_state_template" -CONF_CURRENT_TEMPERATURE_TEMPLATE = 'current_temperature_template' -CONF_CURRENT_TEMPERATURE_TOPIC = 'current_temperature_topic' +CONF_CURRENT_TEMPERATURE_TEMPLATE = "current_temperature_template" +CONF_CURRENT_TEMPERATURE_TOPIC = "current_temperature_topic" -CONF_PAYLOAD_ON = 'payload_on' -CONF_PAYLOAD_OFF = 'payload_off' +CONF_PAYLOAD_ON = "payload_on" +CONF_PAYLOAD_OFF = "payload_off" -CONF_FAN_MODE_LIST = 'fan_modes' -CONF_MODE_LIST = 'modes' -CONF_SWING_MODE_LIST = 'swing_modes' -CONF_INITIAL = 'initial' -CONF_SEND_IF_OFF = 'send_if_off' +CONF_FAN_MODE_LIST = "fan_modes" +CONF_MODE_LIST = "modes" +CONF_SWING_MODE_LIST = "swing_modes" +CONF_INITIAL = "initial" +CONF_SEND_IF_OFF = "send_if_off" -CONF_MIN_TEMP = 'min_temp' -CONF_MAX_TEMP = 'max_temp' +CONF_MIN_TEMP = "min_temp" +CONF_MAX_TEMP = "max_temp" SCHEMA_BASE = CLIMATE_PLATFORM_SCHEMA.extend(MQTT_BASE_PLATFORM_SCHEMA.schema) -PLATFORM_SCHEMA = SCHEMA_BASE.extend({ - vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_TEMPERATURE_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_AWAY_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic, - vol.Optional(CONF_AUX_COMMAND_TOPIC): mqtt.valid_publish_topic, - - vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_TEMPERATURE_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic, - vol.Optional(CONF_AUX_STATE_TOPIC): mqtt.valid_subscribe_topic, - - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_POWER_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_TEMPERATURE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_AWAY_MODE_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_AUX_STATE_TEMPLATE): cv.template, - vol.Optional(CONF_CURRENT_TEMPERATURE_TEMPLATE): cv.template, - - vol.Optional(CONF_CURRENT_TEMPERATURE_TOPIC): - mqtt.valid_subscribe_topic, - vol.Optional(CONF_FAN_MODE_LIST, - default=[STATE_AUTO, SPEED_LOW, - SPEED_MEDIUM, SPEED_HIGH]): cv.ensure_list, - vol.Optional(CONF_SWING_MODE_LIST, - default=[STATE_ON, STATE_OFF]): cv.ensure_list, - vol.Optional(CONF_MODE_LIST, - default=[STATE_AUTO, STATE_OFF, STATE_COOL, STATE_HEAT, - STATE_DRY, STATE_FAN_ONLY]): cv.ensure_list, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_INITIAL, default=21): cv.positive_int, - vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean, - vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string, - vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string, - - vol.Optional(CONF_MIN_TEMP, default=DEFAULT_MIN_TEMP): vol.Coerce(float), - vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): vol.Coerce(float) - -}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) +PLATFORM_SCHEMA = SCHEMA_BASE.extend( + { + vol.Optional(CONF_POWER_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_TEMPERATURE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_FAN_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_SWING_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_AWAY_MODE_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_HOLD_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_AUX_COMMAND_TOPIC): mqtt.valid_publish_topic, + vol.Optional(CONF_POWER_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_TEMPERATURE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_FAN_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_SWING_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_AWAY_MODE_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_HOLD_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_AUX_STATE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_POWER_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_TEMPERATURE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_FAN_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_SWING_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_AWAY_MODE_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_HOLD_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_AUX_STATE_TEMPLATE): cv.template, + vol.Optional(CONF_CURRENT_TEMPERATURE_TEMPLATE): cv.template, + vol.Optional(CONF_CURRENT_TEMPERATURE_TOPIC): mqtt.valid_subscribe_topic, + vol.Optional( + CONF_FAN_MODE_LIST, + default=[STATE_AUTO, SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH], + ): cv.ensure_list, + vol.Optional( + CONF_SWING_MODE_LIST, default=[STATE_ON, STATE_OFF] + ): cv.ensure_list, + vol.Optional( + CONF_MODE_LIST, + default=[ + STATE_AUTO, + STATE_OFF, + STATE_COOL, + STATE_HEAT, + STATE_DRY, + STATE_FAN_ONLY, + ], + ): cv.ensure_list, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_INITIAL, default=21): cv.positive_int, + vol.Optional(CONF_SEND_IF_OFF, default=True): cv.boolean, + vol.Optional(CONF_PAYLOAD_ON, default="ON"): cv.string, + vol.Optional(CONF_PAYLOAD_OFF, default="OFF"): cv.string, + vol.Optional(CONF_MIN_TEMP, default=DEFAULT_MIN_TEMP): vol.Coerce(float), + vol.Optional(CONF_MAX_TEMP, default=DEFAULT_MAX_TEMP): vol.Coerce(float), + } +).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) @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 MQTT climate devices.""" if discovery_info is not None: config = PLATFORM_SCHEMA(discovery_info) @@ -142,7 +170,7 @@ def async_setup_platform(hass, config, async_add_entities, CONF_AWAY_MODE_STATE_TEMPLATE, CONF_HOLD_STATE_TEMPLATE, CONF_AUX_STATE_TEMPLATE, - CONF_CURRENT_TEMPERATURE_TEMPLATE + CONF_CURRENT_TEMPERATURE_TEMPLATE, ) value_templates = {} if CONF_VALUE_TEMPLATE in config: @@ -153,64 +181,93 @@ def async_setup_platform(hass, config, async_add_entities, value_templates[key] = config.get(key) value_templates[key].hass = hass - async_add_entities([ - MqttClimate( - hass, - config.get(CONF_NAME), - { - key: config.get(key) for key in ( - CONF_POWER_COMMAND_TOPIC, - CONF_MODE_COMMAND_TOPIC, - CONF_TEMPERATURE_COMMAND_TOPIC, - CONF_FAN_MODE_COMMAND_TOPIC, - CONF_SWING_MODE_COMMAND_TOPIC, - CONF_AWAY_MODE_COMMAND_TOPIC, - CONF_HOLD_COMMAND_TOPIC, - CONF_AUX_COMMAND_TOPIC, - CONF_POWER_STATE_TOPIC, - CONF_MODE_STATE_TOPIC, - CONF_TEMPERATURE_STATE_TOPIC, - CONF_FAN_MODE_STATE_TOPIC, - CONF_SWING_MODE_STATE_TOPIC, - CONF_AWAY_MODE_STATE_TOPIC, - CONF_HOLD_STATE_TOPIC, - CONF_AUX_STATE_TOPIC, - CONF_CURRENT_TEMPERATURE_TOPIC - ) - }, - value_templates, - config.get(CONF_QOS), - config.get(CONF_RETAIN), - config.get(CONF_MODE_LIST), - config.get(CONF_FAN_MODE_LIST), - config.get(CONF_SWING_MODE_LIST), - config.get(CONF_INITIAL), - False, None, SPEED_LOW, - STATE_OFF, STATE_OFF, False, - config.get(CONF_SEND_IF_OFF), - config.get(CONF_PAYLOAD_ON), - config.get(CONF_PAYLOAD_OFF), - config.get(CONF_AVAILABILITY_TOPIC), - config.get(CONF_PAYLOAD_AVAILABLE), - config.get(CONF_PAYLOAD_NOT_AVAILABLE), - config.get(CONF_MIN_TEMP), - config.get(CONF_MAX_TEMP)) - ]) + async_add_entities( + [ + MqttClimate( + hass, + config.get(CONF_NAME), + { + key: config.get(key) + for key in ( + CONF_POWER_COMMAND_TOPIC, + CONF_MODE_COMMAND_TOPIC, + CONF_TEMPERATURE_COMMAND_TOPIC, + CONF_FAN_MODE_COMMAND_TOPIC, + CONF_SWING_MODE_COMMAND_TOPIC, + CONF_AWAY_MODE_COMMAND_TOPIC, + CONF_HOLD_COMMAND_TOPIC, + CONF_AUX_COMMAND_TOPIC, + CONF_POWER_STATE_TOPIC, + CONF_MODE_STATE_TOPIC, + CONF_TEMPERATURE_STATE_TOPIC, + CONF_FAN_MODE_STATE_TOPIC, + CONF_SWING_MODE_STATE_TOPIC, + CONF_AWAY_MODE_STATE_TOPIC, + CONF_HOLD_STATE_TOPIC, + CONF_AUX_STATE_TOPIC, + CONF_CURRENT_TEMPERATURE_TOPIC, + ) + }, + value_templates, + config.get(CONF_QOS), + config.get(CONF_RETAIN), + config.get(CONF_MODE_LIST), + config.get(CONF_FAN_MODE_LIST), + config.get(CONF_SWING_MODE_LIST), + config.get(CONF_INITIAL), + False, + None, + SPEED_LOW, + STATE_OFF, + STATE_OFF, + False, + config.get(CONF_SEND_IF_OFF), + config.get(CONF_PAYLOAD_ON), + config.get(CONF_PAYLOAD_OFF), + config.get(CONF_AVAILABILITY_TOPIC), + config.get(CONF_PAYLOAD_AVAILABLE), + config.get(CONF_PAYLOAD_NOT_AVAILABLE), + config.get(CONF_MIN_TEMP), + config.get(CONF_MAX_TEMP), + ) + ] + ) class MqttClimate(MqttAvailability, ClimateDevice): """Representation of an MQTT climate device.""" - def __init__(self, hass, name, topic, value_templates, qos, retain, - mode_list, fan_mode_list, swing_mode_list, - target_temperature, away, hold, current_fan_mode, - current_swing_mode, current_operation, aux, send_if_off, - payload_on, payload_off, availability_topic, - payload_available, payload_not_available, - min_temp, max_temp): + def __init__( + self, + hass, + name, + topic, + value_templates, + qos, + retain, + mode_list, + fan_mode_list, + swing_mode_list, + target_temperature, + away, + hold, + current_fan_mode, + current_swing_mode, + current_operation, + aux, + send_if_off, + payload_on, + payload_off, + availability_topic, + payload_available, + payload_not_available, + min_temp, + max_temp, + ): """Initialize the climate device.""" - super().__init__(availability_topic, qos, payload_available, - payload_not_available) + super().__init__( + availability_topic, qos, payload_available, payload_not_available + ) self.hass = hass self._name = name self._topic = topic @@ -245,9 +302,9 @@ class MqttClimate(MqttAvailability, ClimateDevice): def handle_current_temp_received(topic, payload, qos): """Handle current temperature coming via MQTT.""" if CONF_CURRENT_TEMPERATURE_TEMPLATE in self._value_templates: - payload =\ - self._value_templates[CONF_CURRENT_TEMPERATURE_TEMPLATE].\ - async_render_with_possible_json_value(payload) + payload = self._value_templates[ + CONF_CURRENT_TEMPERATURE_TEMPLATE + ].async_render_with_possible_json_value(payload) try: self._current_temperature = float(payload) @@ -257,15 +314,19 @@ class MqttClimate(MqttAvailability, ClimateDevice): if self._topic[CONF_CURRENT_TEMPERATURE_TOPIC] is not None: yield from mqtt.async_subscribe( - self.hass, self._topic[CONF_CURRENT_TEMPERATURE_TOPIC], - handle_current_temp_received, self._qos) + self.hass, + self._topic[CONF_CURRENT_TEMPERATURE_TOPIC], + handle_current_temp_received, + self._qos, + ) @callback def handle_mode_received(topic, payload, qos): """Handle receiving mode via MQTT.""" if CONF_MODE_STATE_TEMPLATE in self._value_templates: - payload = self._value_templates[CONF_MODE_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) + payload = self._value_templates[ + CONF_MODE_STATE_TEMPLATE + ].async_render_with_possible_json_value(payload) if payload not in self._operation_list: _LOGGER.error("Invalid mode: %s", payload) @@ -275,16 +336,19 @@ class MqttClimate(MqttAvailability, ClimateDevice): if self._topic[CONF_MODE_STATE_TOPIC] is not None: yield from mqtt.async_subscribe( - self.hass, self._topic[CONF_MODE_STATE_TOPIC], - handle_mode_received, self._qos) + self.hass, + self._topic[CONF_MODE_STATE_TOPIC], + handle_mode_received, + self._qos, + ) @callback def handle_temperature_received(topic, payload, qos): """Handle target temperature coming via MQTT.""" if CONF_TEMPERATURE_STATE_TEMPLATE in self._value_templates: - payload = \ - self._value_templates[CONF_TEMPERATURE_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) + payload = self._value_templates[ + CONF_TEMPERATURE_STATE_TEMPLATE + ].async_render_with_possible_json_value(payload) try: self._target_temperature = float(payload) @@ -294,16 +358,19 @@ class MqttClimate(MqttAvailability, ClimateDevice): if self._topic[CONF_TEMPERATURE_STATE_TOPIC] is not None: yield from mqtt.async_subscribe( - self.hass, self._topic[CONF_TEMPERATURE_STATE_TOPIC], - handle_temperature_received, self._qos) + self.hass, + self._topic[CONF_TEMPERATURE_STATE_TOPIC], + handle_temperature_received, + self._qos, + ) @callback def handle_fan_mode_received(topic, payload, qos): """Handle receiving fan mode via MQTT.""" if CONF_FAN_MODE_STATE_TEMPLATE in self._value_templates: - payload = \ - self._value_templates[CONF_FAN_MODE_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) + payload = self._value_templates[ + CONF_FAN_MODE_STATE_TEMPLATE + ].async_render_with_possible_json_value(payload) if payload not in self._fan_list: _LOGGER.error("Invalid fan mode: %s", payload) @@ -313,16 +380,19 @@ class MqttClimate(MqttAvailability, ClimateDevice): if self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None: yield from mqtt.async_subscribe( - self.hass, self._topic[CONF_FAN_MODE_STATE_TOPIC], - handle_fan_mode_received, self._qos) + self.hass, + self._topic[CONF_FAN_MODE_STATE_TOPIC], + handle_fan_mode_received, + self._qos, + ) @callback def handle_swing_mode_received(topic, payload, qos): """Handle receiving swing mode via MQTT.""" if CONF_SWING_MODE_STATE_TEMPLATE in self._value_templates: - payload = \ - self._value_templates[CONF_SWING_MODE_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) + payload = self._value_templates[ + CONF_SWING_MODE_STATE_TEMPLATE + ].async_render_with_possible_json_value(payload) if payload not in self._swing_list: _LOGGER.error("Invalid swing mode: %s", payload) @@ -332,16 +402,19 @@ class MqttClimate(MqttAvailability, ClimateDevice): if self._topic[CONF_SWING_MODE_STATE_TOPIC] is not None: yield from mqtt.async_subscribe( - self.hass, self._topic[CONF_SWING_MODE_STATE_TOPIC], - handle_swing_mode_received, self._qos) + self.hass, + self._topic[CONF_SWING_MODE_STATE_TOPIC], + handle_swing_mode_received, + self._qos, + ) @callback def handle_away_mode_received(topic, payload, qos): """Handle receiving away mode via MQTT.""" if CONF_AWAY_MODE_STATE_TEMPLATE in self._value_templates: - payload = \ - self._value_templates[CONF_AWAY_MODE_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) + payload = self._value_templates[ + CONF_AWAY_MODE_STATE_TEMPLATE + ].async_render_with_possible_json_value(payload) if payload == "True": payload = self._payload_on elif payload == "False": @@ -358,15 +431,19 @@ class MqttClimate(MqttAvailability, ClimateDevice): if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None: yield from mqtt.async_subscribe( - self.hass, self._topic[CONF_AWAY_MODE_STATE_TOPIC], - handle_away_mode_received, self._qos) + self.hass, + self._topic[CONF_AWAY_MODE_STATE_TOPIC], + handle_away_mode_received, + self._qos, + ) @callback def handle_aux_mode_received(topic, payload, qos): """Handle receiving aux mode via MQTT.""" if CONF_AUX_STATE_TEMPLATE in self._value_templates: - payload = self._value_templates[CONF_AUX_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) + payload = self._value_templates[ + CONF_AUX_STATE_TEMPLATE + ].async_render_with_possible_json_value(payload) if payload == "True": payload = self._payload_on elif payload == "False": @@ -383,23 +460,30 @@ class MqttClimate(MqttAvailability, ClimateDevice): if self._topic[CONF_AUX_STATE_TOPIC] is not None: yield from mqtt.async_subscribe( - self.hass, self._topic[CONF_AUX_STATE_TOPIC], - handle_aux_mode_received, self._qos) + self.hass, + self._topic[CONF_AUX_STATE_TOPIC], + handle_aux_mode_received, + self._qos, + ) @callback def handle_hold_mode_received(topic, payload, qos): """Handle receiving hold mode via MQTT.""" if CONF_HOLD_STATE_TEMPLATE in self._value_templates: - payload = self._value_templates[CONF_HOLD_STATE_TEMPLATE].\ - async_render_with_possible_json_value(payload) + payload = self._value_templates[ + CONF_HOLD_STATE_TEMPLATE + ].async_render_with_possible_json_value(payload) self._hold = payload self.async_schedule_update_ha_state() if self._topic[CONF_HOLD_STATE_TOPIC] is not None: yield from mqtt.async_subscribe( - self.hass, self._topic[CONF_HOLD_STATE_TOPIC], - handle_hold_mode_received, self._qos) + self.hass, + self._topic[CONF_HOLD_STATE_TOPIC], + handle_hold_mode_received, + self._qos, + ) @property def should_poll(self): @@ -480,8 +564,12 @@ class MqttClimate(MqttAvailability, ClimateDevice): if self._send_if_off or self._current_operation != STATE_OFF: mqtt.async_publish( - self.hass, self._topic[CONF_TEMPERATURE_COMMAND_TOPIC], - kwargs.get(ATTR_TEMPERATURE), self._qos, self._retain) + self.hass, + self._topic[CONF_TEMPERATURE_COMMAND_TOPIC], + kwargs.get(ATTR_TEMPERATURE), + self._qos, + self._retain, + ) self.async_schedule_update_ha_state() @@ -490,8 +578,12 @@ class MqttClimate(MqttAvailability, ClimateDevice): """Set new swing mode.""" if self._send_if_off or self._current_operation != STATE_OFF: mqtt.async_publish( - self.hass, self._topic[CONF_SWING_MODE_COMMAND_TOPIC], - swing_mode, self._qos, self._retain) + self.hass, + self._topic[CONF_SWING_MODE_COMMAND_TOPIC], + swing_mode, + self._qos, + self._retain, + ) if self._topic[CONF_SWING_MODE_STATE_TOPIC] is None: self._current_swing_mode = swing_mode @@ -502,8 +594,12 @@ class MqttClimate(MqttAvailability, ClimateDevice): """Set new target temperature.""" if self._send_if_off or self._current_operation != STATE_OFF: mqtt.async_publish( - self.hass, self._topic[CONF_FAN_MODE_COMMAND_TOPIC], - fan_mode, self._qos, self._retain) + self.hass, + self._topic[CONF_FAN_MODE_COMMAND_TOPIC], + fan_mode, + self._qos, + self._retain, + ) if self._topic[CONF_FAN_MODE_STATE_TOPIC] is None: self._current_fan_mode = fan_mode @@ -513,21 +609,31 @@ class MqttClimate(MqttAvailability, ClimateDevice): def async_set_operation_mode(self, operation_mode) -> None: """Set new operation mode.""" if self._topic[CONF_POWER_COMMAND_TOPIC] is not None: - if (self._current_operation == STATE_OFF and - operation_mode != STATE_OFF): + if self._current_operation == STATE_OFF and operation_mode != STATE_OFF: mqtt.async_publish( - self.hass, self._topic[CONF_POWER_COMMAND_TOPIC], - self._payload_on, self._qos, self._retain) - elif (self._current_operation != STATE_OFF and - operation_mode == STATE_OFF): + self.hass, + self._topic[CONF_POWER_COMMAND_TOPIC], + self._payload_on, + self._qos, + self._retain, + ) + elif self._current_operation != STATE_OFF and operation_mode == STATE_OFF: mqtt.async_publish( - self.hass, self._topic[CONF_POWER_COMMAND_TOPIC], - self._payload_off, self._qos, self._retain) + self.hass, + self._topic[CONF_POWER_COMMAND_TOPIC], + self._payload_off, + self._qos, + self._retain, + ) if self._topic[CONF_MODE_COMMAND_TOPIC] is not None: mqtt.async_publish( - self.hass, self._topic[CONF_MODE_COMMAND_TOPIC], - operation_mode, self._qos, self._retain) + self.hass, + self._topic[CONF_MODE_COMMAND_TOPIC], + operation_mode, + self._qos, + self._retain, + ) if self._topic[CONF_MODE_STATE_TOPIC] is None: self._current_operation = operation_mode @@ -547,9 +653,13 @@ class MqttClimate(MqttAvailability, ClimateDevice): def async_turn_away_mode_on(self): """Turn away mode on.""" if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None: - mqtt.async_publish(self.hass, - self._topic[CONF_AWAY_MODE_COMMAND_TOPIC], - self._payload_on, self._qos, self._retain) + mqtt.async_publish( + self.hass, + self._topic[CONF_AWAY_MODE_COMMAND_TOPIC], + self._payload_on, + self._qos, + self._retain, + ) if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None: self._away = True @@ -559,9 +669,13 @@ class MqttClimate(MqttAvailability, ClimateDevice): def async_turn_away_mode_off(self): """Turn away mode off.""" if self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None: - mqtt.async_publish(self.hass, - self._topic[CONF_AWAY_MODE_COMMAND_TOPIC], - self._payload_off, self._qos, self._retain) + mqtt.async_publish( + self.hass, + self._topic[CONF_AWAY_MODE_COMMAND_TOPIC], + self._payload_off, + self._qos, + self._retain, + ) if self._topic[CONF_AWAY_MODE_STATE_TOPIC] is None: self._away = False @@ -571,9 +685,13 @@ class MqttClimate(MqttAvailability, ClimateDevice): def async_set_hold_mode(self, hold_mode): """Update hold mode on.""" if self._topic[CONF_HOLD_COMMAND_TOPIC] is not None: - mqtt.async_publish(self.hass, - self._topic[CONF_HOLD_COMMAND_TOPIC], - hold_mode, self._qos, self._retain) + mqtt.async_publish( + self.hass, + self._topic[CONF_HOLD_COMMAND_TOPIC], + hold_mode, + self._qos, + self._retain, + ) if self._topic[CONF_HOLD_STATE_TOPIC] is None: self._hold = hold_mode @@ -583,8 +701,13 @@ class MqttClimate(MqttAvailability, ClimateDevice): def async_turn_aux_heat_on(self): """Turn auxiliary heater on.""" if self._topic[CONF_AUX_COMMAND_TOPIC] is not None: - mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC], - self._payload_on, self._qos, self._retain) + mqtt.async_publish( + self.hass, + self._topic[CONF_AUX_COMMAND_TOPIC], + self._payload_on, + self._qos, + self._retain, + ) if self._topic[CONF_AUX_STATE_TOPIC] is None: self._aux = True @@ -594,8 +717,13 @@ class MqttClimate(MqttAvailability, ClimateDevice): def async_turn_aux_heat_off(self): """Turn auxiliary heater off.""" if self._topic[CONF_AUX_COMMAND_TOPIC] is not None: - mqtt.async_publish(self.hass, self._topic[CONF_AUX_COMMAND_TOPIC], - self._payload_off, self._qos, self._retain) + mqtt.async_publish( + self.hass, + self._topic[CONF_AUX_COMMAND_TOPIC], + self._payload_off, + self._qos, + self._retain, + ) if self._topic[CONF_AUX_STATE_TOPIC] is None: self._aux = False @@ -606,32 +734,39 @@ class MqttClimate(MqttAvailability, ClimateDevice): """Return the list of supported features.""" support = 0 - if (self._topic[CONF_TEMPERATURE_STATE_TOPIC] is not None) or \ - (self._topic[CONF_TEMPERATURE_COMMAND_TOPIC] is not None): + if (self._topic[CONF_TEMPERATURE_STATE_TOPIC] is not None) or ( + self._topic[CONF_TEMPERATURE_COMMAND_TOPIC] is not None + ): support |= SUPPORT_TARGET_TEMPERATURE - if (self._topic[CONF_MODE_COMMAND_TOPIC] is not None) or \ - (self._topic[CONF_MODE_STATE_TOPIC] is not None): + if (self._topic[CONF_MODE_COMMAND_TOPIC] is not None) or ( + self._topic[CONF_MODE_STATE_TOPIC] is not None + ): support |= SUPPORT_OPERATION_MODE - if (self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None) or \ - (self._topic[CONF_FAN_MODE_COMMAND_TOPIC] is not None): + if (self._topic[CONF_FAN_MODE_STATE_TOPIC] is not None) or ( + self._topic[CONF_FAN_MODE_COMMAND_TOPIC] is not None + ): support |= SUPPORT_FAN_MODE - if (self._topic[CONF_SWING_MODE_STATE_TOPIC] is not None) or \ - (self._topic[CONF_SWING_MODE_COMMAND_TOPIC] is not None): + if (self._topic[CONF_SWING_MODE_STATE_TOPIC] is not None) or ( + self._topic[CONF_SWING_MODE_COMMAND_TOPIC] is not None + ): support |= SUPPORT_SWING_MODE - if (self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None) or \ - (self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None): + if (self._topic[CONF_AWAY_MODE_STATE_TOPIC] is not None) or ( + self._topic[CONF_AWAY_MODE_COMMAND_TOPIC] is not None + ): support |= SUPPORT_AWAY_MODE - if (self._topic[CONF_HOLD_STATE_TOPIC] is not None) or \ - (self._topic[CONF_HOLD_COMMAND_TOPIC] is not None): + if (self._topic[CONF_HOLD_STATE_TOPIC] is not None) or ( + self._topic[CONF_HOLD_COMMAND_TOPIC] is not None + ): support |= SUPPORT_HOLD_MODE - if (self._topic[CONF_AUX_STATE_TOPIC] is not None) or \ - (self._topic[CONF_AUX_COMMAND_TOPIC] is not None): + if (self._topic[CONF_AUX_STATE_TOPIC] is not None) or ( + self._topic[CONF_AUX_COMMAND_TOPIC] is not None + ): support |= SUPPORT_AUX_HEAT return support diff --git a/homeassistant/components/climate/mysensors.py b/homeassistant/components/climate/mysensors.py index 66c634d8c..4777c3d19 100644 --- a/homeassistant/components/climate/mysensors.py +++ b/homeassistant/components/climate/mysensors.py @@ -6,36 +6,48 @@ https://home-assistant.io/components/climate.mysensors/ """ from homeassistant.components import mysensors from homeassistant.components.climate import ( - ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, DOMAIN, STATE_AUTO, - STATE_COOL, STATE_HEAT, STATE_OFF, SUPPORT_FAN_MODE, - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, - ClimateDevice) + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + DOMAIN, + STATE_AUTO, + STATE_COOL, + STATE_HEAT, + STATE_OFF, + SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_HIGH, + SUPPORT_TARGET_TEMPERATURE_LOW, + ClimateDevice, +) from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS, TEMP_FAHRENHEIT DICT_HA_TO_MYS = { - STATE_AUTO: 'AutoChangeOver', - STATE_COOL: 'CoolOn', - STATE_HEAT: 'HeatOn', - STATE_OFF: 'Off', + STATE_AUTO: "AutoChangeOver", + STATE_COOL: "CoolOn", + STATE_HEAT: "HeatOn", + STATE_OFF: "Off", } DICT_MYS_TO_HA = { - 'AutoChangeOver': STATE_AUTO, - 'CoolOn': STATE_COOL, - 'HeatOn': STATE_HEAT, - 'Off': STATE_OFF, + "AutoChangeOver": STATE_AUTO, + "CoolOn": STATE_COOL, + "HeatOn": STATE_HEAT, + "Off": STATE_OFF, } -FAN_LIST = ['Auto', 'Min', 'Normal', 'Max'] +FAN_LIST = ["Auto", "Min", "Normal", "Max"] OPERATION_LIST = [STATE_OFF, STATE_AUTO, STATE_COOL, STATE_HEAT] -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 mysensors climate.""" mysensors.setup_mysensors_platform( - hass, DOMAIN, discovery_info, MySensorsHVAC, - async_add_entities=async_add_entities) + hass, + DOMAIN, + discovery_info, + MySensorsHVAC, + async_add_entities=async_add_entities, + ) class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice): @@ -48,11 +60,15 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice): set_req = self.gateway.const.SetReq if set_req.V_HVAC_SPEED in self._values: features = features | SUPPORT_FAN_MODE - if (set_req.V_HVAC_SETPOINT_COOL in self._values and - set_req.V_HVAC_SETPOINT_HEAT in self._values): + if ( + set_req.V_HVAC_SETPOINT_COOL in self._values + and set_req.V_HVAC_SETPOINT_HEAT in self._values + ): features = ( - features | SUPPORT_TARGET_TEMPERATURE_HIGH | - SUPPORT_TARGET_TEMPERATURE_LOW) + features + | SUPPORT_TARGET_TEMPERATURE_HIGH + | SUPPORT_TARGET_TEMPERATURE_LOW + ) else: features = features | SUPPORT_TARGET_TEMPERATURE return features @@ -81,8 +97,10 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice): def target_temperature(self): """Return the temperature we try to reach.""" set_req = self.gateway.const.SetReq - if set_req.V_HVAC_SETPOINT_COOL in self._values and \ - set_req.V_HVAC_SETPOINT_HEAT in self._values: + if ( + set_req.V_HVAC_SETPOINT_COOL in self._values + and set_req.V_HVAC_SETPOINT_HEAT in self._values + ): return None temp = self._values.get(set_req.V_HVAC_SETPOINT_COOL) if temp is None: @@ -146,10 +164,10 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice): elif all(val is not None for val in (low, high, heat, cool)): updates = [ (set_req.V_HVAC_SETPOINT_HEAT, low), - (set_req.V_HVAC_SETPOINT_COOL, high)] + (set_req.V_HVAC_SETPOINT_COOL, high), + ] for value_type, value in updates: - self.gateway.set_child_value( - self.node_id, self.child_id, value_type, value) + self.gateway.set_child_value(self.node_id, self.child_id, value_type, value) if self.gateway.optimistic: # Optimistically assume that device has changed state self._values[value_type] = value @@ -159,7 +177,8 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice): """Set new target temperature.""" set_req = self.gateway.const.SetReq self.gateway.set_child_value( - self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan_mode) + self.node_id, self.child_id, set_req.V_HVAC_SPEED, fan_mode + ) if self.gateway.optimistic: # Optimistically assume that device has changed state self._values[set_req.V_HVAC_SPEED] = fan_mode @@ -168,8 +187,8 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice): async def async_set_operation_mode(self, operation_mode): """Set new target temperature.""" self.gateway.set_child_value( - self.node_id, self.child_id, self.value_type, - DICT_HA_TO_MYS[operation_mode]) + self.node_id, self.child_id, self.value_type, DICT_HA_TO_MYS[operation_mode] + ) if self.gateway.optimistic: # Optimistically assume that device has changed state self._values[self.value_type] = operation_mode @@ -178,5 +197,4 @@ class MySensorsHVAC(mysensors.device.MySensorsEntity, ClimateDevice): async def async_update(self): """Update the controller with the latest value from a sensor.""" await super().async_update() - self._values[self.value_type] = DICT_MYS_TO_HA[ - self._values[self.value_type]] + self._values[self.value_type] = DICT_MYS_TO_HA[self._values[self.value_type]] diff --git a/homeassistant/components/climate/nest.py b/homeassistant/components/climate/nest.py index bc63512fc..3eeb06faf 100644 --- a/homeassistant/components/climate/nest.py +++ b/homeassistant/components/climate/nest.py @@ -9,27 +9,45 @@ import logging import voluptuous as vol from homeassistant.components.nest import ( - DATA_NEST, SIGNAL_NEST_UPDATE, DOMAIN as NEST_DOMAIN) + 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, - ATTR_TEMPERATURE, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, - SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE) + STATE_AUTO, + STATE_COOL, + STATE_HEAT, + STATE_ECO, + ClimateDevice, + PLATFORM_SCHEMA, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + ATTR_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_HIGH, + SUPPORT_TARGET_TEMPERATURE_LOW, + SUPPORT_OPERATION_MODE, + SUPPORT_AWAY_MODE, + SUPPORT_FAN_MODE, +) from homeassistant.const import ( - TEMP_CELSIUS, TEMP_FAHRENHEIT, - CONF_SCAN_INTERVAL, STATE_ON, STATE_OFF, STATE_UNKNOWN) + TEMP_CELSIUS, + TEMP_FAHRENHEIT, + CONF_SCAN_INTERVAL, + STATE_ON, + STATE_OFF, + STATE_UNKNOWN, +) from homeassistant.helpers.dispatcher import async_dispatcher_connect -DEPENDENCIES = ['nest'] +DEPENDENCIES = ["nest"] _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_SCAN_INTERVAL): - vol.All(vol.Coerce(int), vol.Range(min=1)), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_SCAN_INTERVAL): vol.All(vol.Coerce(int), vol.Range(min=1))} +) -NEST_MODE_HEAT_COOL = 'heat-cool' +NEST_MODE_HEAT_COOL = "heat-cool" def setup_platform(hass, config, add_entities, discovery_info=None): @@ -45,8 +63,10 @@ async def async_setup_entry(hass, entry, async_add_entities): thermostats = await hass.async_add_job(hass.data[DATA_NEST].thermostats) - all_devices = [NestThermostat(structure, device, temp_unit) - for structure, device in thermostats] + all_devices = [ + NestThermostat(structure, device, temp_unit) + for structure, device in thermostats + ] async_add_entities(all_devices, True) @@ -62,8 +82,9 @@ class NestThermostat(ClimateDevice): self._fan_list = [STATE_ON, STATE_AUTO] # Set the default supported features - self._support_flags = (SUPPORT_TARGET_TEMPERATURE | - SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE) + self._support_flags = ( + SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE + ) # Not all nest devices support cooling and heating remove unused self._operation_list = [STATE_OFF] @@ -77,16 +98,18 @@ class NestThermostat(ClimateDevice): if self.device.can_heat and self.device.can_cool: self._operation_list.append(STATE_AUTO) - self._support_flags = (self._support_flags | - SUPPORT_TARGET_TEMPERATURE_HIGH | - SUPPORT_TARGET_TEMPERATURE_LOW) + self._support_flags = ( + self._support_flags + | SUPPORT_TARGET_TEMPERATURE_HIGH + | SUPPORT_TARGET_TEMPERATURE_LOW + ) self._operation_list.append(STATE_ECO) # feature of device self._has_fan = self.device.has_fan if self._has_fan: - self._support_flags = (self._support_flags | SUPPORT_FAN_MODE) + self._support_flags = self._support_flags | SUPPORT_FAN_MODE # data attributes self._away = None @@ -111,12 +134,12 @@ class NestThermostat(ClimateDevice): async def async_added_to_hass(self): """Register update signal handler.""" + async def async_update_state(): """Update device state.""" await self.async_update_ha_state(True) - async_dispatcher_connect(self.hass, SIGNAL_NEST_UPDATE, - async_update_state) + async_dispatcher_connect(self.hass, SIGNAL_NEST_UPDATE, async_update_state) @property def supported_features(self): @@ -132,13 +155,11 @@ class NestThermostat(ClimateDevice): 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, + "identifiers": {(NEST_DOMAIN, self.device.device_id)}, + "name": self.device.name_long, + "manufacturer": "Nest Labs", + "model": "Thermostat", + "sw_version": self.device.software_version, } @property @@ -168,17 +189,20 @@ class NestThermostat(ClimateDevice): @property def target_temperature(self): """Return the temperature we try to reach.""" - if self._mode != NEST_MODE_HEAT_COOL and \ - self._mode != STATE_ECO and \ - not self.is_away_mode_on: + if ( + self._mode != NEST_MODE_HEAT_COOL + and self._mode != STATE_ECO + and not self.is_away_mode_on + ): return self._target_temperature return None @property def target_temperature_low(self): """Return the lower bound temperature we try to reach.""" - if (self.is_away_mode_on or self._mode == STATE_ECO) and \ - self._eco_temperature[0]: + if (self.is_away_mode_on or self._mode == STATE_ECO) and self._eco_temperature[ + 0 + ]: # eco_temperature is always a low, high tuple return self._eco_temperature[0] if self._mode == NEST_MODE_HEAT_COOL: @@ -188,8 +212,9 @@ class NestThermostat(ClimateDevice): @property def target_temperature_high(self): """Return the upper bound temperature we try to reach.""" - if (self.is_away_mode_on or self._mode == STATE_ECO) and \ - self._eco_temperature[1]: + if (self.is_away_mode_on or self._mode == STATE_ECO) and self._eco_temperature[ + 1 + ]: # eco_temperature is always a low, high tuple return self._eco_temperature[1] if self._mode == NEST_MODE_HEAT_COOL: @@ -204,6 +229,7 @@ class NestThermostat(ClimateDevice): def set_temperature(self, **kwargs): """Set new target temperature.""" import nest + temp = None target_temp_low = kwargs.get(ATTR_TARGET_TEMP_LOW) target_temp_high = kwargs.get(ATTR_TARGET_TEMP_HIGH) @@ -218,8 +244,7 @@ class NestThermostat(ClimateDevice): if temp is not None: self.device.target = temp except nest.nest.APIError as api_error: - _LOGGER.error("An error occurred while setting temperature: %s", - api_error) + _LOGGER.error("An error occurred while setting temperature: %s", api_error) # restore target temperature self.schedule_update_ha_state(True) @@ -233,7 +258,9 @@ class NestThermostat(ClimateDevice): device_mode = STATE_OFF _LOGGER.error( "An error occurred while setting device mode. " - "Invalid operation mode: %s", operation_mode) + "Invalid operation mode: %s", + operation_mode, + ) self.device.mode = device_mode @property @@ -289,13 +316,13 @@ class NestThermostat(ClimateDevice): self._mode = self.device.mode self._target_temperature = self.device.target self._fan = self.device.fan - self._away = self.structure.away == 'away' + self._away = self.structure.away == "away" self._eco_temperature = self.device.eco_temperature self._locked_temperature = self.device.locked_temperature self._min_temperature = self.device.min_temperature self._max_temperature = self.device.max_temperature self._is_locked = self.device.is_locked - if self.device.temperature_scale == 'C': + if self.device.temperature_scale == "C": self._temperature_scale = TEMP_CELSIUS else: self._temperature_scale = TEMP_FAHRENHEIT diff --git a/homeassistant/components/climate/netatmo.py b/homeassistant/components/climate/netatmo.py index 8849ada5c..59793f6ab 100644 --- a/homeassistant/components/climate/netatmo.py +++ b/homeassistant/components/climate/netatmo.py @@ -10,17 +10,23 @@ import voluptuous as vol from homeassistant.const import TEMP_CELSIUS, ATTR_TEMPERATURE from homeassistant.components.climate import ( - STATE_HEAT, STATE_IDLE, ClimateDevice, PLATFORM_SCHEMA, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, SUPPORT_AWAY_MODE) + STATE_HEAT, + STATE_IDLE, + ClimateDevice, + PLATFORM_SCHEMA, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_OPERATION_MODE, + SUPPORT_AWAY_MODE, +) from homeassistant.util import Throttle import homeassistant.helpers.config_validation as cv -DEPENDENCIES = ['netatmo'] +DEPENDENCIES = ["netatmo"] _LOGGER = logging.getLogger(__name__) -CONF_RELAY = 'relay' -CONF_THERMOSTAT = 'thermostat' +CONF_RELAY = "relay" +CONF_THERMOSTAT = "thermostat" DEFAULT_AWAY_TEMPERATURE = 14 # # The default offset is 2 hours (when you use the thermostat itself) @@ -29,14 +35,14 @@ DEFAULT_TIME_OFFSET = 7200 # # NetAtmo Data is uploaded to server every hour MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=300) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_RELAY): cv.string, - vol.Optional(CONF_THERMOSTAT, default=[]): - vol.All(cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_RELAY): cv.string, + vol.Optional(CONF_THERMOSTAT, default=[]): vol.All(cv.ensure_list, [cv.string]), + } +) -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | - SUPPORT_AWAY_MODE) +SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE def setup_platform(hass, config, add_entities, discovery_info=None): @@ -45,12 +51,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): device = config.get(CONF_RELAY) import pyatmo + try: data = ThermostatData(netatmo.NETATMO_AUTH, device) for module_name in data.get_module_names(): if CONF_THERMOSTAT in config: - if config[CONF_THERMOSTAT] != [] and \ - module_name not in config[CONF_THERMOSTAT]: + if ( + config[CONF_THERMOSTAT] != [] + and module_name not in config[CONF_THERMOSTAT] + ): continue add_entities([NetatmoThermostat(data, module_name)], True) except pyatmo.NoDevice: @@ -127,8 +136,7 @@ class NetatmoThermostat(ClimateDevice): if temperature is None: return mode = "manual" - self._data.thermostatdata.setthermpoint( - mode, temperature, DEFAULT_TIME_OFFSET) + self._data.thermostatdata.setthermpoint(mode, temperature, DEFAULT_TIME_OFFSET) self._target_temperature = temperature self._away = False @@ -137,7 +145,7 @@ class NetatmoThermostat(ClimateDevice): """Get the latest data from NetAtmo API and updates the states.""" self._data.update() self._target_temperature = self._data.thermostatdata.setpoint_temp - self._away = self._data.setpoint_mode == 'away' + self._away = self._data.setpoint_mode == "away" class ThermostatData: @@ -159,16 +167,17 @@ class ThermostatData: if not self.device: for device in self.thermostatdata.modules: for module in self.thermostatdata.modules[device].values(): - self.module_names.append(module['module_name']) + self.module_names.append(module["module_name"]) else: for module in self.thermostatdata.modules[self.device].values(): - self.module_names.append(module['module_name']) + self.module_names.append(module["module_name"]) return self.module_names @Throttle(MIN_TIME_BETWEEN_UPDATES) def update(self): """Call the NetAtmo API to update the data.""" import pyatmo + self.thermostatdata = pyatmo.ThermostatData(self.auth) self.target_temperature = self.thermostatdata.setpoint_temp self.setpoint_mode = self.thermostatdata.setpoint_mode diff --git a/homeassistant/components/climate/nuheat.py b/homeassistant/components/climate/nuheat.py index d0bfe5add..e1506717f 100644 --- a/homeassistant/components/climate/nuheat.py +++ b/homeassistant/components/climate/nuheat.py @@ -17,13 +17,15 @@ from homeassistant.components.climate import ( SUPPORT_TARGET_TEMPERATURE, STATE_AUTO, STATE_HEAT, - STATE_IDLE) + STATE_IDLE, +) from homeassistant.components.nuheat import DOMAIN as NUHEAT_DOMAIN from homeassistant.const import ( ATTR_ENTITY_ID, ATTR_TEMPERATURE, TEMP_CELSIUS, - TEMP_FAHRENHEIT) + TEMP_FAHRENHEIT, +) import homeassistant.helpers.config_validation as cv from homeassistant.util import Throttle @@ -48,12 +50,9 @@ SCHEDULE_TEMPORARY_HOLD = 2 SERVICE_RESUME_PROGRAM = "nuheat_resume_program" -RESUME_PROGRAM_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids -}) +RESUME_PROGRAM_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_HOLD_MODE | - SUPPORT_OPERATION_MODE) +SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_HOLD_MODE | SUPPORT_OPERATION_MODE def setup_platform(hass, config, add_entities, discovery_info=None): @@ -73,8 +72,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): """Resume the program on the target thermostats.""" entity_id = service.data.get(ATTR_ENTITY_ID) if entity_id: - target_thermostats = [device for device in thermostats - if device.entity_id in entity_id] + target_thermostats = [ + device for device in thermostats if device.entity_id in entity_id + ] else: target_thermostats = thermostats @@ -84,8 +84,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): thermostat.schedule_update_ha_state(True) hass.services.register( - DOMAIN, SERVICE_RESUME_PROGRAM, resume_program_set_service, - schema=RESUME_PROGRAM_SCHEMA) + DOMAIN, + SERVICE_RESUME_PROGRAM, + resume_program_set_service, + schema=RESUME_PROGRAM_SCHEMA, + ) class NuHeatThermostat(ClimateDevice): @@ -209,7 +212,9 @@ class NuHeatThermostat(ClimateDevice): _LOGGER.debug( "Setting NuHeat thermostat temperature to %s %s", - temperature, self.temperature_unit) + temperature, + self.temperature_unit, + ) self._force_update = True diff --git a/homeassistant/components/climate/oem.py b/homeassistant/components/climate/oem.py index e00624233..7485ac516 100644 --- a/homeassistant/components/climate/oem.py +++ b/homeassistant/components/climate/oem.py @@ -14,26 +14,40 @@ import voluptuous as vol # Import the device class from the component that you want to support from homeassistant.components.climate import ( - ClimateDevice, PLATFORM_SCHEMA, STATE_HEAT, STATE_IDLE, ATTR_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_AWAY_MODE) -from homeassistant.const import (CONF_HOST, CONF_USERNAME, CONF_PASSWORD, - CONF_PORT, TEMP_CELSIUS, CONF_NAME) + ClimateDevice, + PLATFORM_SCHEMA, + STATE_HEAT, + STATE_IDLE, + ATTR_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_AWAY_MODE, +) +from homeassistant.const import ( + CONF_HOST, + CONF_USERNAME, + CONF_PASSWORD, + CONF_PORT, + TEMP_CELSIUS, + CONF_NAME, +) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['oemthermostat==1.1'] +REQUIREMENTS = ["oemthermostat==1.1"] _LOGGER = logging.getLogger(__name__) -CONF_AWAY_TEMP = 'away_temp' +CONF_AWAY_TEMP = "away_temp" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default="Thermostat"): cv.string, - vol.Optional(CONF_PORT, default=80): cv.port, - vol.Inclusive(CONF_USERNAME, 'authentication'): cv.string, - vol.Inclusive(CONF_PASSWORD, 'authentication'): cv.string, - vol.Optional(CONF_AWAY_TEMP, default=14): vol.Coerce(float) -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default="Thermostat"): cv.string, + vol.Optional(CONF_PORT, default=80): cv.port, + vol.Inclusive(CONF_USERNAME, "authentication"): cv.string, + vol.Inclusive(CONF_PASSWORD, "authentication"): cv.string, + vol.Optional(CONF_AWAY_TEMP, default=14): vol.Coerce(float), + } +) SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_AWAY_MODE @@ -50,12 +64,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): away_temp = config.get(CONF_AWAY_TEMP) try: - therm = Thermostat( - host, port=port, username=username, password=password) + therm = Thermostat(host, port=port, username=username, password=password) except (ValueError, AssertionError, requests.RequestException): return False - add_entities((ThermostatDevice(hass, therm, name, away_temp), ), True) + add_entities((ThermostatDevice(hass, therm, name, away_temp),), True) class ThermostatDevice(ClimateDevice): diff --git a/homeassistant/components/climate/opentherm_gw.py b/homeassistant/components/climate/opentherm_gw.py index c1f7afa61..a3537153f 100644 --- a/homeassistant/components/climate/opentherm_gw.py +++ b/homeassistant/components/climate/opentherm_gw.py @@ -8,34 +8,46 @@ 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) +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'] +REQUIREMENTS = ["pyotgw==0.1b0"] CONF_FLOOR_TEMP = "floor_temperature" -CONF_PRECISION = 'precision' +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, -}) +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) +SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE _LOGGER = logging.getLogger(__name__) -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 opentherm_gw device.""" gateway = OpenThermGateway(config) async_add_entities([gateway]) @@ -47,6 +59,7 @@ class OpenThermGateway(ClimateDevice): def __init__(self, config): """Initialize the sensor.""" import pyotgw + self.pyotgw = pyotgw self.gateway = self.pyotgw.pyotgw() self._device = config[CONF_DEVICE] @@ -65,8 +78,7 @@ class OpenThermGateway(ClimateDevice): """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) + _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.""" @@ -103,11 +115,13 @@ class OpenThermGateway(ClimateDevice): 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) + 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._away_state_b = ( + status.get(self.pyotgw.OTGW_GPIO_B_STATE) == self._away_mode_b + ) self.async_schedule_update_ha_state() @property @@ -169,8 +183,7 @@ class OpenThermGateway(ClimateDevice): """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._target_temperature = await self.gateway.set_target_temp(temp) self.async_schedule_update_ha_state() @property diff --git a/homeassistant/components/climate/proliphix.py b/homeassistant/components/climate/proliphix.py index 76160a28c..cbf1056b7 100644 --- a/homeassistant/components/climate/proliphix.py +++ b/homeassistant/components/climate/proliphix.py @@ -7,21 +7,34 @@ https://home-assistant.io/components/climate.proliphix/ import voluptuous as vol from homeassistant.components.climate import ( - PRECISION_TENTHS, STATE_COOL, STATE_HEAT, STATE_IDLE, - ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE) + PRECISION_TENTHS, + STATE_COOL, + STATE_HEAT, + STATE_IDLE, + ClimateDevice, + PLATFORM_SCHEMA, + SUPPORT_TARGET_TEMPERATURE, +) from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_USERNAME, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, + TEMP_FAHRENHEIT, + ATTR_TEMPERATURE, +) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['proliphix==0.4.1'] +REQUIREMENTS = ["proliphix==0.4.1"] -ATTR_FAN = 'fan' +ATTR_FAN = "fan" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -77,9 +90,7 @@ class ProliphixThermostat(ClimateDevice): @property def device_state_attributes(self): """Return the device specific state attributes.""" - return { - ATTR_FAN: self._pdp.fan_state - } + return {ATTR_FAN: self._pdp.fan_state} @property def temperature_unit(self): diff --git a/homeassistant/components/climate/radiotherm.py b/homeassistant/components/climate/radiotherm.py index 14cd2a0f0..ebf0565a9 100644 --- a/homeassistant/components/climate/radiotherm.py +++ b/homeassistant/components/climate/radiotherm.py @@ -11,23 +11,37 @@ import logging import voluptuous as vol from homeassistant.components.climate import ( - STATE_AUTO, STATE_COOL, STATE_HEAT, STATE_IDLE, STATE_ON, STATE_OFF, - ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE, SUPPORT_AWAY_MODE) + STATE_AUTO, + STATE_COOL, + STATE_HEAT, + STATE_IDLE, + STATE_ON, + STATE_OFF, + ClimateDevice, + PLATFORM_SCHEMA, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_OPERATION_MODE, + SUPPORT_FAN_MODE, + SUPPORT_AWAY_MODE, +) from homeassistant.const import ( - CONF_HOST, TEMP_FAHRENHEIT, ATTR_TEMPERATURE, PRECISION_HALVES) + CONF_HOST, + TEMP_FAHRENHEIT, + ATTR_TEMPERATURE, + PRECISION_HALVES, +) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['radiotherm==1.4.1'] +REQUIREMENTS = ["radiotherm==1.4.1"] _LOGGER = logging.getLogger(__name__) -ATTR_FAN = 'fan' -ATTR_MODE = 'mode' +ATTR_FAN = "fan" +ATTR_MODE = "mode" -CONF_HOLD_TEMP = 'hold_temp' -CONF_AWAY_TEMPERATURE_HEAT = 'away_temperature_heat' -CONF_AWAY_TEMPERATURE_COOL = 'away_temperature_cool' +CONF_HOLD_TEMP = "hold_temp" +CONF_AWAY_TEMPERATURE_HEAT = "away_temperature_heat" +CONF_AWAY_TEMPERATURE_COOL = "away_temperature_cool" DEFAULT_AWAY_TEMPERATURE_HEAT = 60 DEFAULT_AWAY_TEMPERATURE_COOL = 85 @@ -68,19 +82,25 @@ def round_temp(temperature): return round(temperature * 2.0) / 2.0 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean, - vol.Optional(CONF_AWAY_TEMPERATURE_HEAT, - default=DEFAULT_AWAY_TEMPERATURE_HEAT): - vol.All(vol.Coerce(float), round_temp), - vol.Optional(CONF_AWAY_TEMPERATURE_COOL, - default=DEFAULT_AWAY_TEMPERATURE_COOL): - vol.All(vol.Coerce(float), round_temp), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HOST): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(CONF_HOLD_TEMP, default=False): cv.boolean, + vol.Optional( + CONF_AWAY_TEMPERATURE_HEAT, default=DEFAULT_AWAY_TEMPERATURE_HEAT + ): vol.All(vol.Coerce(float), round_temp), + vol.Optional( + CONF_AWAY_TEMPERATURE_COOL, default=DEFAULT_AWAY_TEMPERATURE_COOL + ): vol.All(vol.Coerce(float), round_temp), + } +) -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | - SUPPORT_FAN_MODE | SUPPORT_AWAY_MODE) +SUPPORT_FLAGS = ( + SUPPORT_TARGET_TEMPERATURE + | SUPPORT_OPERATION_MODE + | SUPPORT_FAN_MODE + | SUPPORT_AWAY_MODE +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -100,7 +120,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): hold_temp = config.get(CONF_HOLD_TEMP) away_temps = [ config.get(CONF_AWAY_TEMPERATURE_HEAT), - config.get(CONF_AWAY_TEMPERATURE_COOL) + config.get(CONF_AWAY_TEMPERATURE_COOL), ] tstats = [] @@ -109,8 +129,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): tstat = radiotherm.get_thermostat(host) tstats.append(RadioThermostat(tstat, hold_temp, away_temps)) except OSError: - _LOGGER.exception("Unable to connect to Radio Thermostat: %s", - host) + _LOGGER.exception("Unable to connect to Radio Thermostat: %s", host) add_entities(tstats, True) @@ -137,8 +156,8 @@ class RadioThermostat(ClimateDevice): # Fan circulate mode is only supported by the CT80 models. import radiotherm - self._is_model_ct80 = isinstance( - self.device, radiotherm.thermostat.CT80) + + self._is_model_ct80 = isinstance(self.device, radiotherm.thermostat.CT80) @property def supported_features(self): @@ -173,10 +192,7 @@ class RadioThermostat(ClimateDevice): @property def device_state_attributes(self): """Return the device specific state attributes.""" - return { - ATTR_FAN: self._fstate, - ATTR_MODE: self._tstate, - } + return {ATTR_FAN: self._fstate, ATTR_MODE: self._tstate} @property def fan_list(self): @@ -234,37 +250,36 @@ class RadioThermostat(ClimateDevice): # First time - get the name from the thermostat. This is # normally set in the radio thermostat web app. if self._name is None: - self._name = self.device.name['raw'] + self._name = self.device.name["raw"] # Request the current state from the thermostat. - data = self.device.tstat['raw'] + data = self.device.tstat["raw"] - current_temp = data['temp'] + current_temp = data["temp"] if current_temp == -1: - _LOGGER.error('%s (%s) was busy (temp == -1)', self._name, - self.device.host) + _LOGGER.error("%s (%s) was busy (temp == -1)", self._name, self.device.host) return # Map thermostat values into various STATE_ flags. self._current_temperature = current_temp - self._fmode = CODE_TO_FAN_MODE[data['fmode']] - self._fstate = CODE_TO_FAN_STATE[data['fstate']] - self._tmode = CODE_TO_TEMP_MODE[data['tmode']] - self._tstate = CODE_TO_TEMP_STATE[data['tstate']] + self._fmode = CODE_TO_FAN_MODE[data["fmode"]] + self._fstate = CODE_TO_FAN_STATE[data["fstate"]] + self._tmode = CODE_TO_TEMP_MODE[data["tmode"]] + self._tstate = CODE_TO_TEMP_STATE[data["tstate"]] self._current_operation = self._tmode if self._tmode == STATE_COOL: - self._target_temperature = data['t_cool'] + self._target_temperature = data["t_cool"] elif self._tmode == STATE_HEAT: - self._target_temperature = data['t_heat'] + self._target_temperature = data["t_heat"] elif self._tmode == STATE_AUTO: # This doesn't really work - tstate is only set if the HVAC is # active. If it's idle, we don't know what to do with the target # temperature. if self._tstate == STATE_COOL: - self._target_temperature = data['t_cool'] + self._target_temperature = data["t_cool"] elif self._tstate == STATE_HEAT: - self._target_temperature = data['t_heat'] + self._target_temperature = data["t_heat"] else: self._current_operation = STATE_IDLE @@ -288,7 +303,7 @@ class RadioThermostat(ClimateDevice): # Only change the hold if requested or if hold mode was turned # on and we haven't set it yet. - if kwargs.get('hold_changed', False) or not self._hold_set: + if kwargs.get("hold_changed", False) or not self._hold_set: if self._hold_temp or self._away: self.device.hold = 1 self._hold_set = True @@ -301,9 +316,9 @@ class RadioThermostat(ClimateDevice): # reverts to the scheduled temperature. now = datetime.datetime.now() self.device.time = { - 'day': now.weekday(), - 'hour': now.hour, - 'minute': now.minute + "day": now.weekday(), + "hour": now.hour, + "minute": now.minute, } def set_operation_mode(self, operation_mode): diff --git a/homeassistant/components/climate/sensibo.py b/homeassistant/components/climate/sensibo.py index ef33ee849..dbcc04b36 100644 --- a/homeassistant/components/climate/sensibo.py +++ b/homeassistant/components/climate/sensibo.py @@ -13,70 +13,90 @@ import async_timeout import voluptuous as vol from homeassistant.const import ( - ATTR_ENTITY_ID, ATTR_STATE, ATTR_TEMPERATURE, CONF_API_KEY, CONF_ID, - STATE_ON, STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT) + ATTR_ENTITY_ID, + ATTR_STATE, + ATTR_TEMPERATURE, + CONF_API_KEY, + CONF_ID, + STATE_ON, + STATE_OFF, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) from homeassistant.components.climate import ( - ATTR_CURRENT_HUMIDITY, ClimateDevice, DOMAIN, PLATFORM_SCHEMA, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE, - SUPPORT_FAN_MODE, SUPPORT_SWING_MODE, - SUPPORT_ON_OFF) + ATTR_CURRENT_HUMIDITY, + ClimateDevice, + DOMAIN, + PLATFORM_SCHEMA, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_OPERATION_MODE, + SUPPORT_FAN_MODE, + SUPPORT_SWING_MODE, + SUPPORT_ON_OFF, +) from homeassistant.exceptions import PlatformNotReady from homeassistant.helpers import config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util.temperature import convert as convert_temperature -REQUIREMENTS = ['pysensibo==1.0.3'] +REQUIREMENTS = ["pysensibo==1.0.3"] _LOGGER = logging.getLogger(__name__) -ALL = ['all'] +ALL = ["all"] TIMEOUT = 10 -SERVICE_ASSUME_STATE = 'sensibo_assume_state' +SERVICE_ASSUME_STATE = "sensibo_assume_state" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_API_KEY): cv.string, - vol.Optional(CONF_ID, default=ALL): vol.All(cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Optional(CONF_ID, default=ALL): vol.All(cv.ensure_list, [cv.string]), + } +) -ASSUME_STATE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, - vol.Required(ATTR_STATE): cv.string, -}) +ASSUME_STATE_SCHEMA = vol.Schema( + {vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, vol.Required(ATTR_STATE): cv.string} +) -_FETCH_FIELDS = ','.join([ - 'room{name}', 'measurements', 'remoteCapabilities', - 'acState', 'connectionStatus{isAlive}', 'temperatureUnit']) -_INITIAL_FETCH_FIELDS = 'id,' + _FETCH_FIELDS +_FETCH_FIELDS = ",".join( + [ + "room{name}", + "measurements", + "remoteCapabilities", + "acState", + "connectionStatus{isAlive}", + "temperatureUnit", + ] +) +_INITIAL_FETCH_FIELDS = "id," + _FETCH_FIELDS FIELD_TO_FLAG = { - 'fanLevel': SUPPORT_FAN_MODE, - 'mode': SUPPORT_OPERATION_MODE, - 'swing': SUPPORT_SWING_MODE, - 'targetTemperature': SUPPORT_TARGET_TEMPERATURE, - 'on': SUPPORT_ON_OFF, + "fanLevel": SUPPORT_FAN_MODE, + "mode": SUPPORT_OPERATION_MODE, + "swing": SUPPORT_SWING_MODE, + "targetTemperature": SUPPORT_TARGET_TEMPERATURE, + "on": SUPPORT_ON_OFF, } @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 Sensibo devices.""" import pysensibo client = pysensibo.SensiboClient( - config[CONF_API_KEY], session=async_get_clientsession(hass), - timeout=TIMEOUT) + config[CONF_API_KEY], session=async_get_clientsession(hass), timeout=TIMEOUT + ) devices = [] try: - for dev in ( - yield from client.async_get_devices(_INITIAL_FETCH_FIELDS)): - if config[CONF_ID] == ALL or dev['id'] in config[CONF_ID]: - devices.append(SensiboClimate( - client, dev, hass.config.units.temperature_unit)) - except (aiohttp.client_exceptions.ClientConnectorError, - asyncio.TimeoutError): - _LOGGER.exception('Failed to connect to Sensibo servers.') + for dev in (yield from client.async_get_devices(_INITIAL_FETCH_FIELDS)): + if config[CONF_ID] == ALL or dev["id"] in config[CONF_ID]: + devices.append( + SensiboClimate(client, dev, hass.config.units.temperature_unit) + ) + except (aiohttp.client_exceptions.ClientConnectorError, asyncio.TimeoutError): + _LOGGER.exception("Failed to connect to Sensibo servers.") raise PlatformNotReady if devices: @@ -87,22 +107,23 @@ def async_setup_platform(hass, config, async_add_entities, """Set state according to external service call..""" entity_ids = service.data.get(ATTR_ENTITY_ID) if entity_ids: - target_climate = [device for device in devices - if device.entity_id in entity_ids] + target_climate = [ + device for device in devices if device.entity_id in entity_ids + ] else: target_climate = devices update_tasks = [] for climate in target_climate: - yield from climate.async_assume_state( - service.data.get(ATTR_STATE)) + yield from climate.async_assume_state(service.data.get(ATTR_STATE)) update_tasks.append(climate.async_update_ha_state(True)) if update_tasks: yield from asyncio.wait(update_tasks, loop=hass.loop) + hass.services.async_register( - DOMAIN, SERVICE_ASSUME_STATE, async_assume_state, - schema=ASSUME_STATE_SCHEMA) + DOMAIN, SERVICE_ASSUME_STATE, async_assume_state, schema=ASSUME_STATE_SCHEMA + ) class SensiboClimate(ClimateDevice): @@ -115,7 +136,7 @@ class SensiboClimate(ClimateDevice): data: initially-fetched data. """ self._client = client - self._id = data['id'] + self._id = data["id"] self._external_state = None self._units = units self._do_update(data) @@ -126,21 +147,25 @@ class SensiboClimate(ClimateDevice): return self._supported_features def _do_update(self, data): - self._name = data['room']['name'] - self._measurements = data['measurements'] - self._ac_states = data['acState'] - self._status = data['connectionStatus']['isAlive'] - capabilities = data['remoteCapabilities'] - self._operations = sorted(capabilities['modes'].keys()) - self._current_capabilities = capabilities[ - 'modes'][self.current_operation] - temperature_unit_key = data.get('temperatureUnit') or \ - self._ac_states.get('temperatureUnit') + self._name = data["room"]["name"] + self._measurements = data["measurements"] + self._ac_states = data["acState"] + self._status = data["connectionStatus"]["isAlive"] + capabilities = data["remoteCapabilities"] + self._operations = sorted(capabilities["modes"].keys()) + self._current_capabilities = capabilities["modes"][self.current_operation] + temperature_unit_key = data.get("temperatureUnit") or self._ac_states.get( + "temperatureUnit" + ) if temperature_unit_key: - self._temperature_unit = TEMP_CELSIUS if \ - temperature_unit_key == 'C' else TEMP_FAHRENHEIT - self._temperatures_list = self._current_capabilities[ - 'temperatures'].get(temperature_unit_key, {}).get('values', []) + self._temperature_unit = ( + TEMP_CELSIUS if temperature_unit_key == "C" else TEMP_FAHRENHEIT + ) + self._temperatures_list = ( + self._current_capabilities["temperatures"] + .get(temperature_unit_key, {}) + .get("values", []) + ) else: self._temperature_unit = self._units self._temperatures_list = [] @@ -157,8 +182,10 @@ class SensiboClimate(ClimateDevice): @property def device_state_attributes(self): """Return the state attributes.""" - return {ATTR_CURRENT_HUMIDITY: self.current_humidity, - 'battery': self.current_battery} + return { + ATTR_CURRENT_HUMIDITY: self.current_humidity, + "battery": self.current_battery, + } @property def temperature_unit(self): @@ -173,7 +200,7 @@ class SensiboClimate(ClimateDevice): @property def target_temperature(self): """Return the temperature we try to reach.""" - return self._ac_states.get('targetTemperature') + return self._ac_states.get("targetTemperature") @property def target_temperature_step(self): @@ -188,17 +215,17 @@ class SensiboClimate(ClimateDevice): @property def current_operation(self): """Return current operation ie. heat, cool, idle.""" - return self._ac_states['mode'] + return self._ac_states["mode"] @property def current_humidity(self): """Return the current humidity.""" - return self._measurements['humidity'] + return self._measurements["humidity"] @property def current_battery(self): """Return the current battery voltage.""" - return self._measurements.get('batteryVoltage') + return self._measurements.get("batteryVoltage") @property def current_temperature(self): @@ -206,9 +233,8 @@ class SensiboClimate(ClimateDevice): # This field is not affected by temperatureUnit. # It is always in C return convert_temperature( - self._measurements['temperature'], - TEMP_CELSIUS, - self.temperature_unit) + self._measurements["temperature"], TEMP_CELSIUS, self.temperature_unit + ) @property def operation_list(self): @@ -218,22 +244,22 @@ class SensiboClimate(ClimateDevice): @property def current_fan_mode(self): """Return the fan setting.""" - return self._ac_states.get('fanLevel') + return self._ac_states.get("fanLevel") @property def fan_list(self): """List of available fan modes.""" - return self._current_capabilities.get('fanLevels') + return self._current_capabilities.get("fanLevels") @property def current_swing_mode(self): """Return the fan setting.""" - return self._ac_states.get('swing') + return self._ac_states.get("swing") @property def swing_list(self): """List of available swing modes.""" - return self._current_capabilities.get('swing') + return self._current_capabilities.get("swing") @property def name(self): @@ -243,19 +269,21 @@ class SensiboClimate(ClimateDevice): @property def is_on(self): """Return true if AC is on.""" - return self._ac_states['on'] + return self._ac_states["on"] @property def min_temp(self): """Return the minimum temperature.""" - return self._temperatures_list[0] \ - if self._temperatures_list else super().min_temp + return ( + self._temperatures_list[0] if self._temperatures_list else super().min_temp + ) @property def max_temp(self): """Return the maximum temperature.""" - return self._temperatures_list[-1] \ - if self._temperatures_list else super().max_temp + return ( + self._temperatures_list[-1] if self._temperatures_list else super().max_temp + ) @property def unique_id(self): @@ -274,8 +302,10 @@ class SensiboClimate(ClimateDevice): if temperature == self.target_temperature: return index = self._temperatures_list.index(self.target_temperature) - if temperature > self.target_temperature and index < len( - self._temperatures_list) - 1: + if ( + temperature > self.target_temperature + and index < len(self._temperatures_list) - 1 + ): temperature = self._temperatures_list[index + 1] elif temperature < self.target_temperature and index > 0: temperature = self._temperatures_list[index - 1] @@ -284,56 +314,63 @@ class SensiboClimate(ClimateDevice): with async_timeout.timeout(TIMEOUT): yield from self._client.async_set_ac_state_property( - self._id, 'targetTemperature', temperature, self._ac_states) + self._id, "targetTemperature", temperature, self._ac_states + ) @asyncio.coroutine def async_set_fan_mode(self, fan_mode): """Set new target fan mode.""" with async_timeout.timeout(TIMEOUT): yield from self._client.async_set_ac_state_property( - self._id, 'fanLevel', fan_mode, self._ac_states) + self._id, "fanLevel", fan_mode, self._ac_states + ) @asyncio.coroutine def async_set_operation_mode(self, operation_mode): """Set new target operation mode.""" with async_timeout.timeout(TIMEOUT): yield from self._client.async_set_ac_state_property( - self._id, 'mode', operation_mode, self._ac_states) + self._id, "mode", operation_mode, self._ac_states + ) @asyncio.coroutine def async_set_swing_mode(self, swing_mode): """Set new target swing operation.""" with async_timeout.timeout(TIMEOUT): yield from self._client.async_set_ac_state_property( - self._id, 'swing', swing_mode, self._ac_states) + self._id, "swing", swing_mode, self._ac_states + ) @asyncio.coroutine def async_turn_on(self): """Turn Sensibo unit on.""" with async_timeout.timeout(TIMEOUT): yield from self._client.async_set_ac_state_property( - self._id, 'on', True, self._ac_states) + self._id, "on", True, self._ac_states + ) @asyncio.coroutine def async_turn_off(self): """Turn Sensibo unit on.""" with async_timeout.timeout(TIMEOUT): yield from self._client.async_set_ac_state_property( - self._id, 'on', False, self._ac_states) + self._id, "on", False, self._ac_states + ) @asyncio.coroutine def async_assume_state(self, state): """Set external state.""" - change_needed = (state != STATE_OFF and not self.is_on) \ - or (state == STATE_OFF and self.is_on) + change_needed = (state != STATE_OFF and not self.is_on) or ( + state == STATE_OFF and self.is_on + ) if change_needed: with async_timeout.timeout(TIMEOUT): yield from self._client.async_set_ac_state_property( self._id, - 'on', + "on", state != STATE_OFF, # value self._ac_states, - True # assumed_state + True, # assumed_state ) if state in [STATE_ON, STATE_OFF]: @@ -346,8 +383,7 @@ class SensiboClimate(ClimateDevice): """Retrieve latest state.""" try: with async_timeout.timeout(TIMEOUT): - data = yield from self._client.async_get_device( - self._id, _FETCH_FIELDS) + data = yield from self._client.async_get_device(self._id, _FETCH_FIELDS) self._do_update(data) except aiohttp.client_exceptions.ClientError: - _LOGGER.warning('Failed to connect to Sensibo servers.') + _LOGGER.warning("Failed to connect to Sensibo servers.") diff --git a/homeassistant/components/climate/spider.py b/homeassistant/components/climate/spider.py index a9d966bd4..65c815402 100644 --- a/homeassistant/components/climate/spider.py +++ b/homeassistant/components/climate/spider.py @@ -8,23 +8,22 @@ https://home-assistant.io/components/climate.spider/ import logging from homeassistant.components.climate import ( - ATTR_TEMPERATURE, STATE_COOL, STATE_HEAT, STATE_IDLE, - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) + ATTR_TEMPERATURE, + STATE_COOL, + STATE_HEAT, + STATE_IDLE, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + ClimateDevice, +) from homeassistant.components.spider import DOMAIN as SPIDER_DOMAIN from homeassistant.const import TEMP_CELSIUS -DEPENDENCIES = ['spider'] +DEPENDENCIES = ["spider"] -OPERATION_LIST = [ - STATE_HEAT, - STATE_COOL, -] +OPERATION_LIST = [STATE_HEAT, STATE_COOL] -HA_STATE_TO_SPIDER = { - STATE_COOL: 'Cool', - STATE_HEAT: 'Heat', - STATE_IDLE: 'Idle' -} +HA_STATE_TO_SPIDER = {STATE_COOL: "Cool", STATE_HEAT: "Heat", STATE_IDLE: "Idle"} SPIDER_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_SPIDER.items()} @@ -36,8 +35,10 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if discovery_info is None: return - devices = [SpiderThermostat(hass.data[SPIDER_DOMAIN]['controller'], device) - for device in hass.data[SPIDER_DOMAIN]['thermostats']] + devices = [ + SpiderThermostat(hass.data[SPIDER_DOMAIN]["controller"], device) + for device in hass.data[SPIDER_DOMAIN]["thermostats"] + ] add_entities(devices, True) @@ -119,8 +120,7 @@ class SpiderThermostat(ClimateDevice): def set_operation_mode(self, operation_mode): """Set new target operation mode.""" - self.thermostat.set_operation_mode( - HA_STATE_TO_SPIDER.get(operation_mode)) + self.thermostat.set_operation_mode(HA_STATE_TO_SPIDER.get(operation_mode)) def update(self): """Get the latest data.""" diff --git a/homeassistant/components/climate/tado.py b/homeassistant/components/climate/tado.py index 1e52c1636..265b4127f 100644 --- a/homeassistant/components/climate/tado.py +++ b/homeassistant/components/climate/tado.py @@ -6,43 +6,46 @@ https://home-assistant.io/components/climate.tado/ """ import logging -from homeassistant.const import (PRECISION_TENTHS, TEMP_CELSIUS) +from homeassistant.const import PRECISION_TENTHS, TEMP_CELSIUS from homeassistant.components.climate import ( - ClimateDevice, SUPPORT_TARGET_TEMPERATURE, SUPPORT_OPERATION_MODE) + ClimateDevice, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_OPERATION_MODE, +) from homeassistant.util.temperature import convert as convert_temperature from homeassistant.const import ATTR_TEMPERATURE from homeassistant.components.tado import DATA_TADO _LOGGER = logging.getLogger(__name__) -CONST_MODE_SMART_SCHEDULE = 'SMART_SCHEDULE' # Default mytado mode -CONST_MODE_OFF = 'OFF' # Switch off heating in a zone +CONST_MODE_SMART_SCHEDULE = "SMART_SCHEDULE" # Default mytado mode +CONST_MODE_OFF = "OFF" # Switch off heating in a zone # When we change the temperature setting, we need an overlay mode # wait until tado changes the mode automatic -CONST_OVERLAY_TADO_MODE = 'TADO_MODE' +CONST_OVERLAY_TADO_MODE = "TADO_MODE" # the user has change the temperature or mode manually -CONST_OVERLAY_MANUAL = 'MANUAL' +CONST_OVERLAY_MANUAL = "MANUAL" # the temperature will be reset after a timespan -CONST_OVERLAY_TIMER = 'TIMER' +CONST_OVERLAY_TIMER = "TIMER" -CONST_MODE_FAN_HIGH = 'HIGH' -CONST_MODE_FAN_MIDDLE = 'MIDDLE' -CONST_MODE_FAN_LOW = 'LOW' +CONST_MODE_FAN_HIGH = "HIGH" +CONST_MODE_FAN_MIDDLE = "MIDDLE" +CONST_MODE_FAN_LOW = "LOW" FAN_MODES_LIST = { - CONST_MODE_FAN_HIGH: 'High', - CONST_MODE_FAN_MIDDLE: 'Middle', - CONST_MODE_FAN_LOW: 'Low', - CONST_MODE_OFF: 'Off', + CONST_MODE_FAN_HIGH: "High", + CONST_MODE_FAN_MIDDLE: "Middle", + CONST_MODE_FAN_LOW: "Low", + CONST_MODE_OFF: "Off", } OPERATION_LIST = { - CONST_OVERLAY_MANUAL: 'Manual', - CONST_OVERLAY_TIMER: 'Timer', - CONST_OVERLAY_TADO_MODE: 'Tado mode', - CONST_MODE_SMART_SCHEDULE: 'Smart schedule', - CONST_MODE_OFF: 'Off', + CONST_OVERLAY_MANUAL: "Manual", + CONST_OVERLAY_TIMER: "Timer", + CONST_OVERLAY_TADO_MODE: "Tado mode", + CONST_MODE_SMART_SCHEDULE: "Smart schedule", + CONST_MODE_OFF: "Off", } SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE @@ -60,8 +63,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): climate_devices = [] for zone in zones: - device = create_climate_device( - tado, hass, zone, zone['name'], zone['id']) + device = create_climate_device(tado, hass, zone, zone["name"], zone["id"]) if not device: continue climate_devices.append(device) @@ -75,32 +77,33 @@ def create_climate_device(tado, hass, zone, name, zone_id): capabilities = tado.get_capabilities(zone_id) unit = TEMP_CELSIUS - ac_mode = capabilities['type'] == 'AIR_CONDITIONING' + ac_mode = capabilities["type"] == "AIR_CONDITIONING" if ac_mode: - temperatures = capabilities['HEAT']['temperatures'] - elif 'temperatures' in capabilities: - temperatures = capabilities['temperatures'] + temperatures = capabilities["HEAT"]["temperatures"] + elif "temperatures" in capabilities: + temperatures = capabilities["temperatures"] else: _LOGGER.debug("Received zone %s has no temperature; not adding", name) return - min_temp = float(temperatures['celsius']['min']) - max_temp = float(temperatures['celsius']['max']) + min_temp = float(temperatures["celsius"]["min"]) + max_temp = float(temperatures["celsius"]["max"]) - data_id = 'zone {} {}'.format(name, zone_id) - device = TadoClimate(tado, - name, zone_id, data_id, - hass.config.units.temperature(min_temp, unit), - hass.config.units.temperature(max_temp, unit), - ac_mode) + data_id = "zone {} {}".format(name, zone_id) + device = TadoClimate( + tado, + name, + zone_id, + data_id, + hass.config.units.temperature(min_temp, unit), + hass.config.units.temperature(max_temp, unit), + ac_mode, + ) - tado.add_sensor(data_id, { - 'id': zone_id, - 'zone': zone, - 'name': name, - 'climate': device - }) + tado.add_sensor( + data_id, {"id": zone_id, "zone": zone, "name": name, "climate": device} + ) return device @@ -108,9 +111,17 @@ def create_climate_device(tado, hass, zone, name, zone_id): class TadoClimate(ClimateDevice): """Representation of a tado climate device.""" - def __init__(self, store, zone_name, zone_id, data_id, - min_temp, max_temp, ac_mode, - tolerance=0.3): + def __init__( + self, + store, + zone_name, + zone_id, + data_id, + min_temp, + max_temp, + ac_mode, + tolerance=0.3, + ): """Initialize of Tado climate device.""" self._store = store self._data_id = data_id @@ -231,14 +242,16 @@ class TadoClimate(ClimateDevice): @property def min_temp(self): """Return the minimum temperature.""" - return convert_temperature(self._min_temp, self._unit, - self.hass.config.units.temperature_unit) + return convert_temperature( + self._min_temp, self._unit, self.hass.config.units.temperature_unit + ) @property def max_temp(self): """Return the maximum temperature.""" - return convert_temperature(self._max_temp, self._unit, - self.hass.config.units.temperature_unit) + return convert_temperature( + self._max_temp, self._unit, self.hass.config.units.temperature_unit + ) def update(self): """Update the state of this climate device.""" @@ -250,37 +263,34 @@ class TadoClimate(ClimateDevice): _LOGGER.debug("Received no data for zone %s", self.zone_name) return - if 'sensorDataPoints' in data: - sensor_data = data['sensorDataPoints'] + if "sensorDataPoints" in data: + sensor_data = data["sensorDataPoints"] unit = TEMP_CELSIUS - if 'insideTemperature' in sensor_data: - temperature = float( - sensor_data['insideTemperature']['celsius']) - self._cur_temp = self.hass.config.units.temperature( - temperature, unit) + if "insideTemperature" in sensor_data: + temperature = float(sensor_data["insideTemperature"]["celsius"]) + self._cur_temp = self.hass.config.units.temperature(temperature, unit) - if 'humidity' in sensor_data: - humidity = float( - sensor_data['humidity']['percentage']) + if "humidity" in sensor_data: + humidity = float(sensor_data["humidity"]["percentage"]) self._cur_humidity = humidity # temperature setting will not exist when device is off - if 'temperature' in data['setting'] and \ - data['setting']['temperature'] is not None: - setting = float( - data['setting']['temperature']['celsius']) - self._target_temp = self.hass.config.units.temperature( - setting, unit) + if ( + "temperature" in data["setting"] + and data["setting"]["temperature"] is not None + ): + setting = float(data["setting"]["temperature"]["celsius"]) + self._target_temp = self.hass.config.units.temperature(setting, unit) - if 'tadoMode' in data: - mode = data['tadoMode'] - self._is_away = mode == 'AWAY' + if "tadoMode" in data: + mode = data["tadoMode"] + self._is_away = mode == "AWAY" - if 'setting' in data: - power = data['setting']['power'] - if power == 'OFF': + if "setting" in data: + power = data["setting"]["power"] + if power == "OFF": self._current_operation = CONST_MODE_OFF self._current_fan = CONST_MODE_OFF # There is no overlay, the mode will always be @@ -296,23 +306,23 @@ class TadoClimate(ClimateDevice): cooling = False fan_speed = CONST_MODE_OFF - if 'overlay' in data: - overlay_data = data['overlay'] + if "overlay" in data: + overlay_data = data["overlay"] overlay = overlay_data is not None if overlay: - termination = overlay_data['termination']['type'] + termination = overlay_data["termination"]["type"] - if 'setting' in overlay_data: - setting_data = overlay_data['setting'] + if "setting" in overlay_data: + setting_data = overlay_data["setting"] setting = setting_data is not None if setting: - if 'mode' in setting_data: - cooling = setting_data['mode'] == 'COOL' + if "mode" in setting_data: + cooling = setting_data["mode"] == "COOL" - if 'fanSpeed' in setting_data: - fan_speed = setting_data['fanSpeed'] + if "fanSpeed" in setting_data: + fan_speed = setting_data["fanSpeed"] if self._device_is_active: # If you set mode manually to off, there will be an overlay @@ -325,32 +335,37 @@ class TadoClimate(ClimateDevice): def _control_heating(self): """Send new target temperature to mytado.""" - if not self._active and None not in ( - self._cur_temp, self._target_temp): + if not self._active and None not in (self._cur_temp, self._target_temp): self._active = True - _LOGGER.info("Obtained current and target temperature. " - "Tado thermostat active") + _LOGGER.info( + "Obtained current and target temperature. " "Tado thermostat active" + ) if not self._active or self._current_operation == self._overlay_mode: return if self._current_operation == CONST_MODE_SMART_SCHEDULE: - _LOGGER.info("Switching mytado.com to SCHEDULE (default) " - "for zone %s", self.zone_name) + _LOGGER.info( + "Switching mytado.com to SCHEDULE (default) " "for zone %s", + self.zone_name, + ) self._store.reset_zone_overlay(self.zone_id) self._overlay_mode = self._current_operation return if self._current_operation == CONST_MODE_OFF: - _LOGGER.info("Switching mytado.com to OFF for zone %s", - self.zone_name) + _LOGGER.info("Switching mytado.com to OFF for zone %s", self.zone_name) self._store.set_zone_overlay(self.zone_id, CONST_OVERLAY_MANUAL) self._overlay_mode = self._current_operation return - _LOGGER.info("Switching mytado.com to %s mode for zone %s", - self._current_operation, self.zone_name) + _LOGGER.info( + "Switching mytado.com to %s mode for zone %s", + self._current_operation, + self.zone_name, + ) self._store.set_zone_overlay( - self.zone_id, self._current_operation, self._target_temp) + self.zone_id, self._current_operation, self._target_temp + ) self._overlay_mode = self._current_operation diff --git a/homeassistant/components/climate/tesla.py b/homeassistant/components/climate/tesla.py index ef5f2227c..ead7575bf 100644 --- a/homeassistant/components/climate/tesla.py +++ b/homeassistant/components/climate/tesla.py @@ -7,16 +7,24 @@ https://home-assistant.io/components/climate.tesla/ import logging from homeassistant.components.climate import ( - ENTITY_ID_FORMAT, SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - ClimateDevice) + ENTITY_ID_FORMAT, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + ClimateDevice, +) from homeassistant.components.tesla import DOMAIN as TESLA_DOMAIN from homeassistant.components.tesla import TeslaDevice from homeassistant.const import ( - ATTR_TEMPERATURE, STATE_OFF, STATE_ON, TEMP_CELSIUS, TEMP_FAHRENHEIT) + ATTR_TEMPERATURE, + STATE_OFF, + STATE_ON, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['tesla'] +DEPENDENCIES = ["tesla"] OPERATION_LIST = [STATE_ON, STATE_OFF] @@ -25,8 +33,10 @@ SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Tesla climate platform.""" - devices = [TeslaThermostat(device, hass.data[TESLA_DOMAIN]['controller']) - for device in hass.data[TESLA_DOMAIN]['devices']['climate']] + devices = [ + TeslaThermostat(device, hass.data[TESLA_DOMAIN]["controller"]) + for device in hass.data[TESLA_DOMAIN]["devices"]["climate"] + ] add_entities(devices, True) @@ -70,7 +80,7 @@ class TeslaThermostat(TeslaDevice, ClimateDevice): """Return the unit of measurement.""" tesla_temp_units = self.tesla_device.measurement - if tesla_temp_units == 'F': + if tesla_temp_units == "F": return TEMP_FAHRENHEIT return TEMP_CELSIUS diff --git a/homeassistant/components/climate/toon.py b/homeassistant/components/climate/toon.py index e759e922e..be14ff9fe 100644 --- a/homeassistant/components/climate/toon.py +++ b/homeassistant/components/climate/toon.py @@ -8,8 +8,15 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/climate.toon/ """ from homeassistant.components.climate import ( - ATTR_TEMPERATURE, STATE_COOL, STATE_ECO, STATE_HEAT, STATE_PERFORMANCE, - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) + ATTR_TEMPERATURE, + STATE_COOL, + STATE_ECO, + STATE_HEAT, + STATE_PERFORMANCE, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + ClimateDevice, +) import homeassistant.components.toon as toon_main from homeassistant.const import TEMP_CELSIUS @@ -26,19 +33,14 @@ class ThermostatDevice(ClimateDevice): def __init__(self, hass): """Initialize the Toon climate device.""" - self._name = 'Toon van Eneco' + self._name = "Toon van Eneco" self.hass = hass self.thermos = hass.data[toon_main.TOON_HANDLE] self._state = None self._temperature = None self._setpoint = None - self._operation_list = [ - STATE_PERFORMANCE, - STATE_HEAT, - STATE_ECO, - STATE_COOL, - ] + self._operation_list = [STATE_PERFORMANCE, STATE_HEAT, STATE_ECO, STATE_COOL] @property def supported_features(self): @@ -58,7 +60,7 @@ class ThermostatDevice(ClimateDevice): @property def current_operation(self): """Return current operation i.e. comfort, home, away.""" - state = self.thermos.get_data('state') + state = self.thermos.get_data("state") return state @property @@ -69,12 +71,12 @@ class ThermostatDevice(ClimateDevice): @property def current_temperature(self): """Return the current temperature.""" - return self.thermos.get_data('temp') + return self.thermos.get_data("temp") @property def target_temperature(self): """Return the temperature we try to reach.""" - return self.thermos.get_data('setpoint') + return self.thermos.get_data("setpoint") def set_temperature(self, **kwargs): """Change the setpoint of the thermostat.""" @@ -84,10 +86,10 @@ class ThermostatDevice(ClimateDevice): def set_operation_mode(self, operation_mode): """Set new operation mode.""" toonlib_values = { - STATE_PERFORMANCE: 'Comfort', - STATE_HEAT: 'Home', - STATE_ECO: 'Away', - STATE_COOL: 'Sleep', + STATE_PERFORMANCE: "Comfort", + STATE_HEAT: "Home", + STATE_ECO: "Away", + STATE_COOL: "Sleep", } self.thermos.set_state(toonlib_values[operation_mode]) diff --git a/homeassistant/components/climate/touchline.py b/homeassistant/components/climate/touchline.py index 641f6e9a1..246b752b0 100644 --- a/homeassistant/components/climate/touchline.py +++ b/homeassistant/components/climate/touchline.py @@ -9,24 +9,26 @@ import logging import voluptuous as vol from homeassistant.components.climate import ( - ClimateDevice, PLATFORM_SCHEMA, SUPPORT_TARGET_TEMPERATURE) + ClimateDevice, + PLATFORM_SCHEMA, + SUPPORT_TARGET_TEMPERATURE, +) from homeassistant.const import CONF_HOST, TEMP_CELSIUS, ATTR_TEMPERATURE import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pytouchline==0.7'] +REQUIREMENTS = ["pytouchline==0.7"] _LOGGER = logging.getLogger(__name__) -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE) +SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Required(CONF_HOST): cv.string}) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Touchline devices.""" from pytouchline import PyTouchline + host = config[CONF_HOST] py_touchline = PyTouchline() number_of_devices = int(py_touchline.get_number_of_devices(host)) diff --git a/homeassistant/components/climate/tuya.py b/homeassistant/components/climate/tuya.py index 2da46fee1..81f70fb13 100644 --- a/homeassistant/components/climate/tuya.py +++ b/homeassistant/components/climate/tuya.py @@ -6,30 +6,43 @@ https://home-assistant.io/components/climate.tuya/ """ from homeassistant.components.climate import ( - ATTR_TEMPERATURE, ENTITY_ID_FORMAT, STATE_AUTO, STATE_COOL, STATE_ECO, - STATE_ELECTRIC, STATE_FAN_ONLY, STATE_GAS, STATE_HEAT, STATE_HEAT_PUMP, - STATE_HIGH_DEMAND, STATE_PERFORMANCE, SUPPORT_FAN_MODE, SUPPORT_ON_OFF, - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) + ATTR_TEMPERATURE, + ENTITY_ID_FORMAT, + STATE_AUTO, + STATE_COOL, + STATE_ECO, + STATE_ELECTRIC, + STATE_FAN_ONLY, + STATE_GAS, + STATE_HEAT, + STATE_HEAT_PUMP, + STATE_HIGH_DEMAND, + STATE_PERFORMANCE, + SUPPORT_FAN_MODE, + SUPPORT_ON_OFF, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + ClimateDevice, +) from homeassistant.components.fan import SPEED_LOW, SPEED_MEDIUM, SPEED_HIGH from homeassistant.components.tuya import DATA_TUYA, TuyaDevice -from homeassistant.const import ( - PRECISION_WHOLE, TEMP_CELSIUS, TEMP_FAHRENHEIT) +from homeassistant.const import PRECISION_WHOLE, TEMP_CELSIUS, TEMP_FAHRENHEIT -DEPENDENCIES = ['tuya'] -DEVICE_TYPE = 'climate' +DEPENDENCIES = ["tuya"] +DEVICE_TYPE = "climate" HA_STATE_TO_TUYA = { - STATE_AUTO: 'auto', - STATE_COOL: 'cold', - STATE_ECO: 'eco', - STATE_ELECTRIC: 'electric', - STATE_FAN_ONLY: 'wind', - STATE_GAS: 'gas', - STATE_HEAT: 'hot', - STATE_HEAT_PUMP: 'heat_pump', - STATE_HIGH_DEMAND: 'high_demand', - STATE_PERFORMANCE: 'performance', + STATE_AUTO: "auto", + STATE_COOL: "cold", + STATE_ECO: "eco", + STATE_ELECTRIC: "electric", + STATE_FAN_ONLY: "wind", + STATE_GAS: "gas", + STATE_HEAT: "hot", + STATE_HEAT_PUMP: "heat_pump", + STATE_HIGH_DEMAND: "high_demand", + STATE_PERFORMANCE: "performance", } TUYA_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_TUYA.items()} @@ -42,7 +55,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if discovery_info is None: return tuya = hass.data[DATA_TUYA] - dev_ids = discovery_info.get('dev_ids') + dev_ids = discovery_info.get("dev_ids") devices = [] for dev_id in dev_ids: device = tuya.get_device_by_id(dev_id) @@ -85,9 +98,9 @@ class TuyaClimateDevice(TuyaDevice, ClimateDevice): def temperature_unit(self): """Return the unit of measurement used by the platform.""" unit = self.tuya.temperature_unit() - if unit == 'CELSIUS': + if unit == "CELSIUS": return TEMP_CELSIUS - if unit == 'FAHRENHEIT': + if unit == "FAHRENHEIT": return TEMP_FAHRENHEIT return TEMP_CELSIUS diff --git a/homeassistant/components/climate/venstar.py b/homeassistant/components/climate/venstar.py index 16c0b2061..ab7e009a8 100644 --- a/homeassistant/components/climate/venstar.py +++ b/homeassistant/components/climate/venstar.py @@ -9,45 +9,69 @@ import logging import voluptuous as vol from homeassistant.components.climate import ( - ATTR_OPERATION_MODE, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - PLATFORM_SCHEMA, STATE_AUTO, STATE_COOL, STATE_HEAT, SUPPORT_FAN_MODE, - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_HUMIDITY, SUPPORT_AWAY_MODE, - SUPPORT_TARGET_HUMIDITY_HIGH, SUPPORT_TARGET_HUMIDITY_LOW, - SUPPORT_HOLD_MODE, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, - ClimateDevice) + ATTR_OPERATION_MODE, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + PLATFORM_SCHEMA, + STATE_AUTO, + STATE_COOL, + STATE_HEAT, + SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_HUMIDITY, + SUPPORT_AWAY_MODE, + SUPPORT_TARGET_HUMIDITY_HIGH, + SUPPORT_TARGET_HUMIDITY_LOW, + SUPPORT_HOLD_MODE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_HIGH, + SUPPORT_TARGET_TEMPERATURE_LOW, + ClimateDevice, +) from homeassistant.const import ( - ATTR_TEMPERATURE, CONF_HOST, CONF_PASSWORD, CONF_SSL, CONF_TIMEOUT, - CONF_USERNAME, PRECISION_WHOLE, STATE_OFF, STATE_ON, TEMP_CELSIUS, - TEMP_FAHRENHEIT) + ATTR_TEMPERATURE, + CONF_HOST, + CONF_PASSWORD, + CONF_SSL, + CONF_TIMEOUT, + CONF_USERNAME, + PRECISION_WHOLE, + STATE_OFF, + STATE_ON, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, +) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['venstarcolortouch==0.6'] +REQUIREMENTS = ["venstarcolortouch==0.6"] _LOGGER = logging.getLogger(__name__) -ATTR_FAN_STATE = 'fan_state' -ATTR_HVAC_STATE = 'hvac_state' +ATTR_FAN_STATE = "fan_state" +ATTR_HVAC_STATE = "hvac_state" -CONF_HUMIDIFIER = 'humidifier' +CONF_HUMIDIFIER = "humidifier" DEFAULT_SSL = False VALID_FAN_STATES = [STATE_ON, STATE_AUTO] VALID_THERMOSTAT_MODES = [STATE_HEAT, STATE_COOL, STATE_OFF, STATE_AUTO] -HOLD_MODE_OFF = 'off' -HOLD_MODE_TEMPERATURE = 'temperature' +HOLD_MODE_OFF = "off" +HOLD_MODE_TEMPERATURE = "temperature" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_HUMIDIFIER, default=True): cv.boolean, - vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, - vol.Optional(CONF_TIMEOUT, default=5): - vol.All(vol.Coerce(int), vol.Range(min=1)), - vol.Optional(CONF_USERNAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_HUMIDIFIER, default=True): cv.boolean, + vol.Optional(CONF_SSL, default=DEFAULT_SSL): cv.boolean, + vol.Optional(CONF_TIMEOUT, default=5): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + vol.Optional(CONF_USERNAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -61,13 +85,13 @@ def setup_platform(hass, config, add_entities, discovery_info=None): humidifier = config.get(CONF_HUMIDIFIER) if config.get(CONF_SSL): - proto = 'https' + proto = "https" else: - proto = 'http' + proto = "http" client = venstarcolortouch.VenstarColorTouch( - addr=host, timeout=timeout, user=username, password=password, - proto=proto) + addr=host, timeout=timeout, user=username, password=password, proto=proto + ) add_entities([VenstarThermostat(client, humidifier)], True) @@ -90,19 +114,23 @@ class VenstarThermostat(ClimateDevice): @property def supported_features(self): """Return the list of supported features.""" - features = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE | - SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE | - SUPPORT_HOLD_MODE) + features = ( + SUPPORT_TARGET_TEMPERATURE + | SUPPORT_FAN_MODE + | SUPPORT_OPERATION_MODE + | SUPPORT_AWAY_MODE + | SUPPORT_HOLD_MODE + ) if self._client.mode == self._client.MODE_AUTO: - features |= (SUPPORT_TARGET_TEMPERATURE_HIGH | - SUPPORT_TARGET_TEMPERATURE_LOW) + features |= SUPPORT_TARGET_TEMPERATURE_HIGH | SUPPORT_TARGET_TEMPERATURE_LOW - if (self._humidifier and - hasattr(self._client, 'hum_active')): - features |= (SUPPORT_TARGET_HUMIDITY | - SUPPORT_TARGET_HUMIDITY_HIGH | - SUPPORT_TARGET_HUMIDITY_LOW) + if self._humidifier and hasattr(self._client, "hum_active"): + features |= ( + SUPPORT_TARGET_HUMIDITY + | SUPPORT_TARGET_HUMIDITY_HIGH + | SUPPORT_TARGET_HUMIDITY_LOW + ) return features @@ -251,16 +279,16 @@ class VenstarThermostat(ClimateDevice): if set_temp: if operation_mode == self._client.MODE_HEAT: - success = self._client.set_setpoints( - temperature, self._client.cooltemp) + success = self._client.set_setpoints(temperature, self._client.cooltemp) elif operation_mode == self._client.MODE_COOL: - success = self._client.set_setpoints( - self._client.heattemp, temperature) + success = self._client.set_setpoints(self._client.heattemp, temperature) elif operation_mode == self._client.MODE_AUTO: success = self._client.set_setpoints(temp_low, temp_high) else: - _LOGGER.error("The thermostat is currently not in a mode " - "that supports target temperature") + _LOGGER.error( + "The thermostat is currently not in a mode " + "that supports target temperature" + ) if not success: _LOGGER.error("Failed to change the temperature") diff --git a/homeassistant/components/climate/vera.py b/homeassistant/components/climate/vera.py index e97bd6cd8..24f98d8a0 100644 --- a/homeassistant/components/climate/vera.py +++ b/homeassistant/components/climate/vera.py @@ -8,32 +8,35 @@ import logging from homeassistant.util import convert from homeassistant.components.climate import ( - ClimateDevice, ENTITY_ID_FORMAT, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_OPERATION_MODE, SUPPORT_FAN_MODE) -from homeassistant.const import ( - TEMP_FAHRENHEIT, - TEMP_CELSIUS, - ATTR_TEMPERATURE) + ClimateDevice, + ENTITY_ID_FORMAT, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_OPERATION_MODE, + SUPPORT_FAN_MODE, +) +from homeassistant.const import TEMP_FAHRENHEIT, TEMP_CELSIUS, ATTR_TEMPERATURE -from homeassistant.components.vera import ( - VERA_CONTROLLER, VERA_DEVICES, VeraDevice) +from homeassistant.components.vera import VERA_CONTROLLER, VERA_DEVICES, VeraDevice -DEPENDENCIES = ['vera'] +DEPENDENCIES = ["vera"] _LOGGER = logging.getLogger(__name__) -OPERATION_LIST = ['Heat', 'Cool', 'Auto Changeover', 'Off'] -FAN_OPERATION_LIST = ['On', 'Auto', 'Cycle'] +OPERATION_LIST = ["Heat", "Cool", "Auto Changeover", "Off"] +FAN_OPERATION_LIST = ["On", "Auto", "Cycle"] -SUPPORT_FLAGS = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | - SUPPORT_FAN_MODE) +SUPPORT_FLAGS = SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_FAN_MODE def setup_platform(hass, config, add_entities_callback, discovery_info=None): """Set up of Vera thermostats.""" add_entities_callback( - [VeraThermostat(device, hass.data[VERA_CONTROLLER]) for - device in hass.data[VERA_DEVICES]['climate']], True) + [ + VeraThermostat(device, hass.data[VERA_CONTROLLER]) + for device in hass.data[VERA_DEVICES]["climate"] + ], + True, + ) class VeraThermostat(VeraDevice, ClimateDevice): @@ -53,15 +56,15 @@ class VeraThermostat(VeraDevice, ClimateDevice): def current_operation(self): """Return current operation ie. heat, cool, idle.""" mode = self.vera_device.get_hvac_mode() - if mode == 'HeatOn': + if mode == "HeatOn": return OPERATION_LIST[0] # heat - if mode == 'CoolOn': + if mode == "CoolOn": return OPERATION_LIST[1] # cool - if mode == 'AutoChangeOver': + if mode == "AutoChangeOver": return OPERATION_LIST[2] # auto - if mode == 'Off': + if mode == "Off": return OPERATION_LIST[3] # off - return 'Off' + return "Off" @property def operation_list(self): @@ -104,10 +107,9 @@ class VeraThermostat(VeraDevice, ClimateDevice): @property def temperature_unit(self): """Return the unit of measurement.""" - vera_temp_units = ( - self.vera_device.vera_controller.temperature_units) + vera_temp_units = self.vera_device.vera_controller.temperature_units - if vera_temp_units == 'F': + if vera_temp_units == "F": return TEMP_FAHRENHEIT return TEMP_CELSIUS diff --git a/homeassistant/components/climate/wink.py b/homeassistant/components/climate/wink.py index 3013a1553..17da2fd91 100644 --- a/homeassistant/components/climate/wink.py +++ b/homeassistant/components/climate/wink.py @@ -8,79 +8,108 @@ import asyncio import logging from homeassistant.components.climate import ( - ATTR_CURRENT_HUMIDITY, ATTR_TARGET_TEMP_HIGH, ATTR_TARGET_TEMP_LOW, - ATTR_TEMPERATURE, STATE_AUTO, STATE_COOL, STATE_ECO, STATE_ELECTRIC, - STATE_FAN_ONLY, STATE_GAS, STATE_HEAT, STATE_HEAT_PUMP, STATE_HIGH_DEMAND, - STATE_PERFORMANCE, SUPPORT_AUX_HEAT, SUPPORT_AWAY_MODE, SUPPORT_FAN_MODE, - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, - SUPPORT_TARGET_TEMPERATURE_HIGH, SUPPORT_TARGET_TEMPERATURE_LOW, - ClimateDevice) + ATTR_CURRENT_HUMIDITY, + ATTR_TARGET_TEMP_HIGH, + ATTR_TARGET_TEMP_LOW, + ATTR_TEMPERATURE, + STATE_AUTO, + STATE_COOL, + STATE_ECO, + STATE_ELECTRIC, + STATE_FAN_ONLY, + STATE_GAS, + STATE_HEAT, + STATE_HEAT_PUMP, + STATE_HIGH_DEMAND, + STATE_PERFORMANCE, + SUPPORT_AUX_HEAT, + SUPPORT_AWAY_MODE, + SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_TARGET_TEMPERATURE_HIGH, + SUPPORT_TARGET_TEMPERATURE_LOW, + ClimateDevice, +) from homeassistant.components.wink import DOMAIN, WinkDevice from homeassistant.const import ( - PRECISION_TENTHS, STATE_OFF, STATE_ON, STATE_UNKNOWN, TEMP_CELSIUS) + PRECISION_TENTHS, + STATE_OFF, + STATE_ON, + STATE_UNKNOWN, + TEMP_CELSIUS, +) from homeassistant.helpers.temperature import display_temp as show_temp _LOGGER = logging.getLogger(__name__) -ATTR_ECO_TARGET = 'eco_target' -ATTR_EXTERNAL_TEMPERATURE = 'external_temperature' -ATTR_OCCUPIED = 'occupied' -ATTR_RHEEM_TYPE = 'rheem_type' -ATTR_SCHEDULE_ENABLED = 'schedule_enabled' -ATTR_SMART_TEMPERATURE = 'smart_temperature' -ATTR_TOTAL_CONSUMPTION = 'total_consumption' -ATTR_VACATION_MODE = 'vacation_mode' -ATTR_HEAT_ON = 'heat_on' -ATTR_COOL_ON = 'cool_on' +ATTR_ECO_TARGET = "eco_target" +ATTR_EXTERNAL_TEMPERATURE = "external_temperature" +ATTR_OCCUPIED = "occupied" +ATTR_RHEEM_TYPE = "rheem_type" +ATTR_SCHEDULE_ENABLED = "schedule_enabled" +ATTR_SMART_TEMPERATURE = "smart_temperature" +ATTR_TOTAL_CONSUMPTION = "total_consumption" +ATTR_VACATION_MODE = "vacation_mode" +ATTR_HEAT_ON = "heat_on" +ATTR_COOL_ON = "cool_on" -DEPENDENCIES = ['wink'] +DEPENDENCIES = ["wink"] -SPEED_LOW = 'low' -SPEED_MEDIUM = 'medium' -SPEED_HIGH = 'high' +SPEED_LOW = "low" +SPEED_MEDIUM = "medium" +SPEED_HIGH = "high" HA_STATE_TO_WINK = { - STATE_AUTO: 'auto', - STATE_COOL: 'cool_only', - STATE_ECO: 'eco', - STATE_ELECTRIC: 'electric_only', - STATE_FAN_ONLY: 'fan_only', - STATE_GAS: 'gas', - STATE_HEAT: 'heat_only', - STATE_HEAT_PUMP: 'heat_pump', - STATE_HIGH_DEMAND: 'high_demand', - STATE_OFF: 'off', - STATE_PERFORMANCE: 'performance', + STATE_AUTO: "auto", + STATE_COOL: "cool_only", + STATE_ECO: "eco", + STATE_ELECTRIC: "electric_only", + STATE_FAN_ONLY: "fan_only", + STATE_GAS: "gas", + STATE_HEAT: "heat_only", + STATE_HEAT_PUMP: "heat_pump", + STATE_HIGH_DEMAND: "high_demand", + STATE_OFF: "off", + STATE_PERFORMANCE: "performance", } WINK_STATE_TO_HA = {value: key for key, value in HA_STATE_TO_WINK.items()} SUPPORT_FLAGS_THERMOSTAT = ( - SUPPORT_TARGET_TEMPERATURE | SUPPORT_TARGET_TEMPERATURE_HIGH | - SUPPORT_TARGET_TEMPERATURE_LOW | SUPPORT_OPERATION_MODE | - SUPPORT_AWAY_MODE | SUPPORT_FAN_MODE | SUPPORT_AUX_HEAT) + SUPPORT_TARGET_TEMPERATURE + | SUPPORT_TARGET_TEMPERATURE_HIGH + | SUPPORT_TARGET_TEMPERATURE_LOW + | SUPPORT_OPERATION_MODE + | SUPPORT_AWAY_MODE + | SUPPORT_FAN_MODE + | SUPPORT_AUX_HEAT +) -SUPPORT_FLAGS_AC = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | - SUPPORT_FAN_MODE) +SUPPORT_FLAGS_AC = ( + SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_FAN_MODE +) -SUPPORT_FLAGS_HEATER = (SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | - SUPPORT_AWAY_MODE) +SUPPORT_FLAGS_HEATER = ( + SUPPORT_TARGET_TEMPERATURE | SUPPORT_OPERATION_MODE | SUPPORT_AWAY_MODE +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Wink climate devices.""" import pywink + for climate in pywink.get_thermostats(): _id = climate.object_id() + climate.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: add_entities([WinkThermostat(climate, hass)]) for climate in pywink.get_air_conditioners(): _id = climate.object_id() + climate.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: add_entities([WinkAC(climate, hass)]) for water_heater in pywink.get_water_heaters(): _id = water_heater.object_id() + water_heater.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: add_entities([WinkWaterHeater(water_heater, hass)]) @@ -95,7 +124,7 @@ class WinkThermostat(WinkDevice, ClimateDevice): @asyncio.coroutine def async_added_to_hass(self): """Call when entity is added to hass.""" - self.hass.data[DOMAIN]['entities']['climate'].append(self) + self.hass.data[DOMAIN]["entities"]["climate"].append(self) @property def temperature_unit(self): @@ -111,17 +140,26 @@ class WinkThermostat(WinkDevice, ClimateDevice): target_temp_low = self.target_temperature_low if target_temp_high is not None: data[ATTR_TARGET_TEMP_HIGH] = show_temp( - self.hass, self.target_temperature_high, self.temperature_unit, - PRECISION_TENTHS) + self.hass, + self.target_temperature_high, + self.temperature_unit, + PRECISION_TENTHS, + ) if target_temp_low is not None: data[ATTR_TARGET_TEMP_LOW] = show_temp( - self.hass, self.target_temperature_low, self.temperature_unit, - PRECISION_TENTHS) + self.hass, + self.target_temperature_low, + self.temperature_unit, + PRECISION_TENTHS, + ) if self.external_temperature is not None: data[ATTR_EXTERNAL_TEMPERATURE] = show_temp( - self.hass, self.external_temperature, self.temperature_unit, - PRECISION_TENTHS) + self.hass, + self.external_temperature, + self.temperature_unit, + PRECISION_TENTHS, + ) if self.smart_temperature: data[ATTR_SMART_TEMPERATURE] = self.smart_temperature @@ -198,7 +236,7 @@ class WinkThermostat(WinkDevice, ClimateDevice): current_op = STATE_OFF else: current_op = WINK_STATE_TO_HA.get(self.wink.current_hvac_mode()) - if current_op == 'aux': + if current_op == "aux": return STATE_HEAT if current_op is None: current_op = STATE_UNKNOWN @@ -208,10 +246,10 @@ class WinkThermostat(WinkDevice, ClimateDevice): def target_humidity(self): """Return the humidity we try to reach.""" target_hum = None - if self.wink.current_humidifier_mode() == 'on': + if self.wink.current_humidifier_mode() == "on": if self.wink.current_humidifier_set_point() is not None: target_hum = self.wink.current_humidifier_set_point() * 100 - elif self.wink.current_dehumidifier_mode() == 'on': + elif self.wink.current_dehumidifier_mode() == "on": if self.wink.current_dehumidifier_set_point() is not None: target_hum = self.wink.current_dehumidifier_set_point() * 100 else: @@ -250,10 +288,10 @@ class WinkThermostat(WinkDevice, ClimateDevice): @property def is_aux_heat_on(self): """Return true if aux heater.""" - if 'aux' not in self.wink.hvac_modes(): + if "aux" not in self.wink.hvac_modes(): return None - if self.wink.current_hvac_mode() == 'aux': + if self.wink.current_hvac_mode() == "aux": return True return False @@ -284,17 +322,20 @@ class WinkThermostat(WinkDevice, ClimateDevice): @property def operation_list(self): """List of available operation modes.""" - op_list = ['off'] + op_list = ["off"] modes = self.wink.hvac_modes() for mode in modes: - if mode == 'aux': + if mode == "aux": continue ha_mode = WINK_STATE_TO_HA.get(mode) if ha_mode is not None: op_list.append(ha_mode) else: - error = "Invalid operation mode mapping. " + mode + \ - " doesn't map. Please report this." + error = ( + "Invalid operation mode mapping. " + + mode + + " doesn't map. Please report this." + ) _LOGGER.error(error) return op_list @@ -309,9 +350,9 @@ class WinkThermostat(WinkDevice, ClimateDevice): @property def current_fan_mode(self): """Return whether the fan is on.""" - if self.wink.current_fan_mode() == 'on': + if self.wink.current_fan_mode() == "on": return STATE_ON - if self.wink.current_fan_mode() == 'auto': + if self.wink.current_fan_mode() == "auto": return STATE_AUTO # No Fan available so disable slider return None @@ -329,7 +370,7 @@ class WinkThermostat(WinkDevice, ClimateDevice): def turn_aux_heat_on(self): """Turn auxiliary heater on.""" - self.wink.set_operation_mode('aux') + self.wink.set_operation_mode("aux") def turn_aux_heat_off(self): """Turn auxiliary heater off.""" @@ -408,12 +449,18 @@ class WinkAC(WinkDevice, ClimateDevice): target_temp_low = self.target_temperature_low if target_temp_high is not None: data[ATTR_TARGET_TEMP_HIGH] = show_temp( - self.hass, self.target_temperature_high, self.temperature_unit, - PRECISION_TENTHS) + self.hass, + self.target_temperature_high, + self.temperature_unit, + PRECISION_TENTHS, + ) if target_temp_low is not None: data[ATTR_TARGET_TEMP_LOW] = show_temp( - self.hass, self.target_temperature_low, self.temperature_unit, - PRECISION_TENTHS) + self.hass, + self.target_temperature_low, + self.temperature_unit, + PRECISION_TENTHS, + ) data[ATTR_TOTAL_CONSUMPTION] = self.wink.total_consumption() data[ATTR_SCHEDULE_ENABLED] = self.wink.schedule_enabled() @@ -441,7 +488,7 @@ class WinkAC(WinkDevice, ClimateDevice): @property def operation_list(self): """List of available operation modes.""" - op_list = ['off'] + op_list = ["off"] modes = self.wink.modes() for mode in modes: if mode == "auto_eco": @@ -450,8 +497,11 @@ class WinkAC(WinkDevice, ClimateDevice): if ha_mode is not None: op_list.append(ha_mode) else: - error = "Invalid operation mode mapping. " + mode + \ - " doesn't map. Please report this." + error = ( + "Invalid operation mode mapping. " + + mode + + " doesn't map. Please report this." + ) _LOGGER.error(error) return op_list @@ -463,8 +513,8 @@ class WinkAC(WinkDevice, ClimateDevice): def set_operation_mode(self, operation_mode): """Set operation mode.""" op_mode_to_set = HA_STATE_TO_WINK.get(operation_mode) - if op_mode_to_set == 'eco': - op_mode_to_set = 'auto_eco' + if op_mode_to_set == "eco": + op_mode_to_set = "auto_eco" self.wink.set_operation_mode(op_mode_to_set) @property @@ -550,17 +600,20 @@ class WinkWaterHeater(WinkDevice, ClimateDevice): @property def operation_list(self): """List of available operation modes.""" - op_list = ['off'] + op_list = ["off"] modes = self.wink.modes() for mode in modes: - if mode == 'aux': + if mode == "aux": continue ha_mode = WINK_STATE_TO_HA.get(mode) if ha_mode is not None: op_list.append(ha_mode) else: - error = "Invalid operation mode mapping. " + mode + \ - " doesn't map. Please report this." + error = ( + "Invalid operation mode mapping. " + + mode + + " doesn't map. Please report this." + ) _LOGGER.error(error) return op_list diff --git a/homeassistant/components/climate/zhong_hong.py b/homeassistant/components/climate/zhong_hong.py index 46d590a94..091398ddd 100644 --- a/homeassistant/components/climate/zhong_hong.py +++ b/homeassistant/components/climate/zhong_hong.py @@ -9,36 +9,52 @@ import logging import voluptuous as vol from homeassistant.components.climate import ( - ATTR_OPERATION_MODE, PLATFORM_SCHEMA, STATE_COOL, STATE_DRY, - STATE_FAN_ONLY, STATE_HEAT, SUPPORT_FAN_MODE, SUPPORT_ON_OFF, - SUPPORT_OPERATION_MODE, SUPPORT_TARGET_TEMPERATURE, ClimateDevice) -from homeassistant.const import (ATTR_TEMPERATURE, CONF_HOST, CONF_PORT, - EVENT_HOMEASSISTANT_STOP, TEMP_CELSIUS) + ATTR_OPERATION_MODE, + PLATFORM_SCHEMA, + STATE_COOL, + STATE_DRY, + STATE_FAN_ONLY, + STATE_HEAT, + SUPPORT_FAN_MODE, + SUPPORT_ON_OFF, + SUPPORT_OPERATION_MODE, + SUPPORT_TARGET_TEMPERATURE, + ClimateDevice, +) +from homeassistant.const import ( + ATTR_TEMPERATURE, + CONF_HOST, + CONF_PORT, + EVENT_HOMEASSISTANT_STOP, + TEMP_CELSIUS, +) import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.dispatcher import (async_dispatcher_connect, - async_dispatcher_send) +from homeassistant.helpers.dispatcher import ( + async_dispatcher_connect, + async_dispatcher_send, +) _LOGGER = logging.getLogger(__name__) -CONF_GATEWAY_ADDRRESS = 'gateway_address' +CONF_GATEWAY_ADDRRESS = "gateway_address" -REQUIREMENTS = ['zhong_hong_hvac==1.0.9'] -SIGNAL_DEVICE_ADDED = 'zhong_hong_device_added' -SIGNAL_ZHONG_HONG_HUB_START = 'zhong_hong_hub_start' +REQUIREMENTS = ["zhong_hong_hvac==1.0.9"] +SIGNAL_DEVICE_ADDED = "zhong_hong_device_added" +SIGNAL_ZHONG_HONG_HUB_START = "zhong_hong_hub_start" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): - cv.string, - vol.Optional(CONF_PORT, default=9999): - vol.Coerce(int), - vol.Optional(CONF_GATEWAY_ADDRRESS, default=1): - vol.Coerce(int), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=9999): vol.Coerce(int), + vol.Optional(CONF_GATEWAY_ADDRRESS, default=1): vol.Coerce(int), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the ZhongHong HVAC platform.""" from zhong_hong_hvac.hub import ZhongHongGateway + host = config.get(CONF_HOST) port = config.get(CONF_PORT) gw_addr = config.get(CONF_GATEWAY_ADDRRESS) @@ -84,6 +100,7 @@ class ZhongHongClimate(ClimateDevice): def __init__(self, hub, addr_out, addr_in): """Set up the ZhongHong climate devices.""" from zhong_hong_hvac.hvac import HVAC + self._device = HVAC(hub, addr_out, addr_in) self._hub = hub self._current_operation = None @@ -126,14 +143,19 @@ class ZhongHongClimate(ClimateDevice): @property def unique_id(self): """Return the unique ID of the HVAC.""" - return "zhong_hong_hvac_{}_{}".format(self._device.addr_out, - self._device.addr_in) + return "zhong_hong_hvac_{}_{}".format( + self._device.addr_out, self._device.addr_in + ) @property def supported_features(self): """Return the list of supported features.""" - return (SUPPORT_TARGET_TEMPERATURE | SUPPORT_FAN_MODE - | SUPPORT_OPERATION_MODE | SUPPORT_ON_OFF) + return ( + SUPPORT_TARGET_TEMPERATURE + | SUPPORT_FAN_MODE + | SUPPORT_OPERATION_MODE + | SUPPORT_ON_OFF + ) @property def temperature_unit(self): diff --git a/homeassistant/components/climate/zwave.py b/homeassistant/components/climate/zwave.py index f87f2e83f..296c7b398 100644 --- a/homeassistant/components/climate/zwave.py +++ b/homeassistant/components/climate/zwave.py @@ -7,38 +7,49 @@ https://home-assistant.io/components/climate.zwave/ # Because we do not compile openzwave on CI import logging from homeassistant.components.climate import ( - DOMAIN, ClimateDevice, STATE_AUTO, STATE_COOL, STATE_HEAT, - SUPPORT_TARGET_TEMPERATURE, SUPPORT_FAN_MODE, - SUPPORT_OPERATION_MODE, SUPPORT_SWING_MODE) + DOMAIN, + ClimateDevice, + STATE_AUTO, + STATE_COOL, + STATE_HEAT, + SUPPORT_TARGET_TEMPERATURE, + SUPPORT_FAN_MODE, + SUPPORT_OPERATION_MODE, + SUPPORT_SWING_MODE, +) from homeassistant.components.zwave import ZWaveDeviceEntity -from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import +from homeassistant.components.zwave import ( + async_setup_platform +) # noqa pylint: disable=unused-import from homeassistant.const import ( - STATE_OFF, TEMP_CELSIUS, TEMP_FAHRENHEIT, ATTR_TEMPERATURE) + STATE_OFF, + TEMP_CELSIUS, + TEMP_FAHRENHEIT, + ATTR_TEMPERATURE, +) _LOGGER = logging.getLogger(__name__) -CONF_NAME = 'name' -DEFAULT_NAME = 'Z-Wave Climate' +CONF_NAME = "name" +DEFAULT_NAME = "Z-Wave Climate" REMOTEC = 0x5254 REMOTEC_ZXT_120 = 0x8377 REMOTEC_ZXT_120_THERMOSTAT = (REMOTEC, REMOTEC_ZXT_120) -ATTR_OPERATING_STATE = 'operating_state' -ATTR_FAN_STATE = 'fan_state' +ATTR_OPERATING_STATE = "operating_state" +ATTR_FAN_STATE = "fan_state" -WORKAROUND_ZXT_120 = 'zxt_120' +WORKAROUND_ZXT_120 = "zxt_120" -DEVICE_MAPPINGS = { - REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120 -} +DEVICE_MAPPINGS = {REMOTEC_ZXT_120_THERMOSTAT: WORKAROUND_ZXT_120} STATE_MAPPINGS = { - 'Off': STATE_OFF, - 'Heat': STATE_HEAT, - 'Heat Mode': STATE_HEAT, - 'Heat (Default)': STATE_HEAT, - 'Cool': STATE_COOL, - 'Auto': STATE_AUTO, + "Off": STATE_OFF, + "Heat": STATE_HEAT, + "Heat Mode": STATE_HEAT, + "Heat (Default)": STATE_HEAT, + "Cool": STATE_COOL, + "Auto": STATE_AUTO, } @@ -69,15 +80,14 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): _LOGGER.debug("temp_unit is %s", self._unit) self._zxt_120 = None # Make sure that we have values for the key before converting to int - if (self.node.manufacturer_id.strip() and - self.node.product_id.strip()): + if self.node.manufacturer_id.strip() and self.node.product_id.strip(): specific_sensor_key = ( int(self.node.manufacturer_id, 16), - int(self.node.product_id, 16)) + int(self.node.product_id, 16), + ) if specific_sensor_key in DEVICE_MAPPINGS: if DEVICE_MAPPINGS[specific_sensor_key] == WORKAROUND_ZXT_120: - _LOGGER.debug( - "Remotec ZXT-120 Zwave Thermostat workaround") + _LOGGER.debug("Remotec ZXT-120 Zwave Thermostat workaround") self._zxt_120 = 1 self.update_properties() @@ -110,8 +120,13 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): self._operation_list.append(mode) current_mode = self.values.mode.data self._current_operation = next( - (key for key, value in self._operation_mapping.items() - if value == current_mode), current_mode) + ( + key + for key, value in self._operation_mapping.items() + if value == current_mode + ), + current_mode, + ) _LOGGER.debug("self._operation_list=%s", self._operation_list) _LOGGER.debug("self._current_operation=%s", self._current_operation) @@ -129,8 +144,7 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): if fan_list: self._fan_list = list(fan_list) _LOGGER.debug("self._fan_list=%s", self._fan_list) - _LOGGER.debug("self._current_fan_mode=%s", - self._current_fan_mode) + _LOGGER.debug("self._current_fan_mode=%s", self._current_fan_mode) # Swing mode if self._zxt_120 == 1: if self.values.zxt_120_swing_mode: @@ -139,19 +153,17 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): if swing_list: self._swing_list = list(swing_list) _LOGGER.debug("self._swing_list=%s", self._swing_list) - _LOGGER.debug("self._current_swing_mode=%s", - self._current_swing_mode) + _LOGGER.debug("self._current_swing_mode=%s", self._current_swing_mode) # Set point if self.values.primary.data == 0: - _LOGGER.debug("Setpoint is 0, setting default to " - "current_temperature=%s", - self._current_temperature) + _LOGGER.debug( + "Setpoint is 0, setting default to " "current_temperature=%s", + self._current_temperature, + ) if self._current_temperature is not None: - self._target_temperature = ( - round((float(self._current_temperature)), 1)) + self._target_temperature = round((float(self._current_temperature)), 1) else: - self._target_temperature = round( - (float(self.values.primary.data)), 1) + self._target_temperature = round((float(self.values.primary.data)), 1) # Operating state if self.values.operating_state: @@ -184,9 +196,9 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): @property def temperature_unit(self): """Return the unit of measurement.""" - if self._unit == 'C': + if self._unit == "C": return TEMP_CELSIUS - if self._unit == 'F': + if self._unit == "F": return TEMP_FAHRENHEIT return self._unit @@ -228,7 +240,8 @@ class ZWaveClimate(ZWaveDeviceEntity, ClimateDevice): """Set new target operation mode.""" if self.values.mode: self.values.mode.data = self._operation_mapping.get( - operation_mode, operation_mode) + operation_mode, operation_mode + ) def set_swing_mode(self, swing_mode): """Set new target swing mode.""" diff --git a/homeassistant/components/cloud/__init__.py b/homeassistant/components/cloud/__init__.py index 8c1a9751c..6eb66bb40 100644 --- a/homeassistant/components/cloud/__init__.py +++ b/homeassistant/components/cloud/__init__.py @@ -15,7 +15,11 @@ import async_timeout import voluptuous as vol from homeassistant.const import ( - EVENT_HOMEASSISTANT_START, CONF_REGION, CONF_MODE, CONF_NAME) + EVENT_HOMEASSISTANT_START, + CONF_REGION, + CONF_MODE, + CONF_NAME, +) from homeassistant.helpers import entityfilter, config_validation as cv from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.util import dt as dt_util @@ -26,63 +30,73 @@ from homeassistant.components.google_assistant import const as ga_c from . import http_api, iot from .const import CONFIG_DIR, DOMAIN, SERVERS -REQUIREMENTS = ['warrant==0.6.1'] +REQUIREMENTS = ["warrant==0.6.1"] _LOGGER = logging.getLogger(__name__) -CONF_ALEXA = 'alexa' -CONF_ALIASES = 'aliases' -CONF_COGNITO_CLIENT_ID = 'cognito_client_id' -CONF_ENTITY_CONFIG = 'entity_config' -CONF_FILTER = 'filter' -CONF_GOOGLE_ACTIONS = 'google_actions' -CONF_RELAYER = 'relayer' -CONF_USER_POOL_ID = 'user_pool_id' -CONF_GOOGLE_ACTIONS_SYNC_URL = 'google_actions_sync_url' +CONF_ALEXA = "alexa" +CONF_ALIASES = "aliases" +CONF_COGNITO_CLIENT_ID = "cognito_client_id" +CONF_ENTITY_CONFIG = "entity_config" +CONF_FILTER = "filter" +CONF_GOOGLE_ACTIONS = "google_actions" +CONF_RELAYER = "relayer" +CONF_USER_POOL_ID = "user_pool_id" +CONF_GOOGLE_ACTIONS_SYNC_URL = "google_actions_sync_url" -DEFAULT_MODE = 'production' -DEPENDENCIES = ['http'] +DEFAULT_MODE = "production" +DEPENDENCIES = ["http"] -MODE_DEV = 'development' +MODE_DEV = "development" -ALEXA_ENTITY_SCHEMA = vol.Schema({ - vol.Optional(alexa_sh.CONF_DESCRIPTION): cv.string, - vol.Optional(alexa_sh.CONF_DISPLAY_CATEGORIES): cv.string, - vol.Optional(alexa_sh.CONF_NAME): cv.string, -}) +ALEXA_ENTITY_SCHEMA = vol.Schema( + { + vol.Optional(alexa_sh.CONF_DESCRIPTION): cv.string, + vol.Optional(alexa_sh.CONF_DISPLAY_CATEGORIES): cv.string, + vol.Optional(alexa_sh.CONF_NAME): cv.string, + } +) -GOOGLE_ENTITY_SCHEMA = vol.Schema({ - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_ALIASES): vol.All(cv.ensure_list, [cv.string]), - vol.Optional(ga_c.CONF_ROOM_HINT): cv.string, -}) +GOOGLE_ENTITY_SCHEMA = vol.Schema( + { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_ALIASES): vol.All(cv.ensure_list, [cv.string]), + vol.Optional(ga_c.CONF_ROOM_HINT): cv.string, + } +) -ASSISTANT_SCHEMA = vol.Schema({ - vol.Optional(CONF_FILTER, default={}): entityfilter.FILTER_SCHEMA, -}) +ASSISTANT_SCHEMA = vol.Schema( + {vol.Optional(CONF_FILTER, default={}): entityfilter.FILTER_SCHEMA} +) -ALEXA_SCHEMA = ASSISTANT_SCHEMA.extend({ - vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA} -}) +ALEXA_SCHEMA = ASSISTANT_SCHEMA.extend( + {vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: ALEXA_ENTITY_SCHEMA}} +) -GACTIONS_SCHEMA = ASSISTANT_SCHEMA.extend({ - vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: GOOGLE_ENTITY_SCHEMA} -}) +GACTIONS_SCHEMA = ASSISTANT_SCHEMA.extend( + {vol.Optional(CONF_ENTITY_CONFIG): {cv.entity_id: GOOGLE_ENTITY_SCHEMA}} +) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_MODE, default=DEFAULT_MODE): - vol.In([MODE_DEV] + list(SERVERS)), - # Change to optional when we include real servers - vol.Optional(CONF_COGNITO_CLIENT_ID): str, - vol.Optional(CONF_USER_POOL_ID): str, - vol.Optional(CONF_REGION): str, - vol.Optional(CONF_RELAYER): str, - vol.Optional(CONF_GOOGLE_ACTIONS_SYNC_URL): str, - vol.Optional(CONF_ALEXA): ALEXA_SCHEMA, - vol.Optional(CONF_GOOGLE_ACTIONS): GACTIONS_SCHEMA, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_MODE, default=DEFAULT_MODE): vol.In( + [MODE_DEV] + list(SERVERS) + ), + # Change to optional when we include real servers + vol.Optional(CONF_COGNITO_CLIENT_ID): str, + vol.Optional(CONF_USER_POOL_ID): str, + vol.Optional(CONF_REGION): str, + vol.Optional(CONF_RELAYER): str, + vol.Optional(CONF_GOOGLE_ACTIONS_SYNC_URL): str, + vol.Optional(CONF_ALEXA): ALEXA_SCHEMA, + vol.Optional(CONF_GOOGLE_ACTIONS): GACTIONS_SCHEMA, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) @asyncio.coroutine @@ -112,9 +126,18 @@ def async_setup(hass, config): class Cloud: """Store the configuration of the cloud connection.""" - def __init__(self, hass, mode, alexa, google_actions, - cognito_client_id=None, user_pool_id=None, region=None, - relayer=None, google_actions_sync_url=None): + def __init__( + self, + hass, + mode, + alexa, + google_actions, + cognito_client_id=None, + user_pool_id=None, + region=None, + relayer=None, + google_actions_sync_url=None, + ): """Create an instance of Cloud.""" self.hass = hass self.mode = mode @@ -137,11 +160,11 @@ class Cloud: else: info = SERVERS[mode] - self.cognito_client_id = info['cognito_client_id'] - self.user_pool_id = info['user_pool_id'] - self.region = info['region'] - self.relayer = info['relayer'] - self.google_actions_sync_url = info['google_actions_sync_url'] + self.cognito_client_id = info["cognito_client_id"] + self.user_pool_id = info["user_pool_id"] + self.region = info["region"] + self.relayer = info["relayer"] + self.google_actions_sync_url = info["google_actions_sync_url"] @property def is_logged_in(self): @@ -157,8 +180,8 @@ class Cloud: def expiration_date(self): """Return the subscription expiration as a UTC datetime object.""" return datetime.combine( - dt_util.parse_date(self.claims['custom:sub-exp']), - datetime.min.time()).replace(tzinfo=dt_util.UTC) + dt_util.parse_date(self.claims["custom:sub-exp"]), datetime.min.time() + ).replace(tzinfo=dt_util.UTC) @property def claims(self): @@ -168,7 +191,7 @@ class Cloud: @property def user_info_path(self): """Get path to the stored auth.""" - return self.path('{}_auth.json'.format(self.mode)) + return self.path("{}_auth.json".format(self.mode)) @property def gactions_config(self): @@ -178,11 +201,11 @@ class Cloud: def should_expose(entity): """If an entity should be exposed.""" - return conf['filter'](entity.entity_id) + return conf["filter"](entity.entity_id) self._gactions_config = ga_h.Config( should_expose=should_expose, - agent_user_id=self.claims['cognito:username'], + agent_user_id=self.claims["cognito:username"], entity_config=conf.get(CONF_ENTITY_CONFIG), ) @@ -205,17 +228,21 @@ class Cloud: self.refresh_token = None self._gactions_config = None - yield from self.hass.async_add_job( - lambda: os.remove(self.user_info_path)) + yield from self.hass.async_add_job(lambda: os.remove(self.user_info_path)) def write_user_info(self): """Write user info to a file.""" - with open(self.user_info_path, 'wt') as file: - file.write(json.dumps({ - 'id_token': self.id_token, - 'access_token': self.access_token, - 'refresh_token': self.refresh_token, - }, indent=4)) + with open(self.user_info_path, "wt") as file: + file.write( + json.dumps( + { + "id_token": self.id_token, + "access_token": self.access_token, + "refresh_token": self.refresh_token, + }, + indent=4, + ) + ) @asyncio.coroutine def async_start(self, _): @@ -238,7 +265,7 @@ class Cloud: if not os.path.isfile(user_info): return None - with open(user_info, 'rt') as file: + with open(user_info, "rt") as file: return json.loads(file.read()) info = yield from self.hass.async_add_job(load_config) @@ -248,15 +275,15 @@ class Cloud: # Validate tokens try: - for token in 'id_token', 'access_token': + for token in "id_token", "access_token": self._decode_claims(info[token]) except ValueError as err: # Raised when token is invalid _LOGGER.warning("Found invalid token %s: %s", token, err) return - self.id_token = info['id_token'] - self.access_token = info['access_token'] - self.refresh_token = info['refresh_token'] + self.id_token = info["id_token"] + self.access_token = info["access_token"] + self.refresh_token = info["refresh_token"] self.hass.add_job(self.iot.connect()) @@ -264,8 +291,10 @@ class Cloud: def _fetch_jwt_keyset(self): """Fetch the JWT keyset for the Cognito instance.""" session = async_get_clientsession(self.hass) - url = ("https://cognito-idp.us-east-1.amazonaws.com/" - "{}/.well-known/jwks.json".format(self.user_pool_id)) + url = ( + "https://cognito-idp.us-east-1.amazonaws.com/" + "{}/.well-known/jwks.json".format(self.user_pool_id) + ) try: with async_timeout.timeout(10, loop=self.hass.loop): @@ -281,29 +310,31 @@ class Cloud: def _decode_claims(self, token): """Decode the claims in a token.""" from jose import jwt, exceptions as jose_exceptions + try: header = jwt.get_unverified_header(token) except jose_exceptions.JWTError as err: raise ValueError(str(err)) from None - kid = header.get('kid') + kid = header.get("kid") if kid is None: raise ValueError("No kid in header") # Locate the key for this kid key = None - for key_dict in self.jwt_keyset['keys']: - if key_dict['kid'] == kid: + for key_dict in self.jwt_keyset["keys"]: + if key_dict["kid"] == kid: key = key_dict break if not key: - raise ValueError( - "Unable to locate kid ({}) in keyset".format(kid)) + raise ValueError("Unable to locate kid ({}) in keyset".format(kid)) try: return jwt.decode( - token, key, audience=self.cognito_client_id, options={ - 'verify_exp': False, - }) + token, + key, + audience=self.cognito_client_id, + options={"verify_exp": False}, + ) except jose_exceptions.JWTError as err: raise ValueError(str(err)) from None diff --git a/homeassistant/components/cloud/auth_api.py b/homeassistant/components/cloud/auth_api.py index dcf756748..ede5c1fa4 100644 --- a/homeassistant/components/cloud/auth_api.py +++ b/homeassistant/components/cloud/auth_api.py @@ -22,7 +22,7 @@ class PasswordChangeRequired(CloudError): # https://github.com/PyCQA/pylint/issues/1085 # pylint: disable=useless-super-delegation - def __init__(self, message='Password change required.'): + def __init__(self, message="Password change required."): """Initialize a password change required error.""" super().__init__(message) @@ -32,17 +32,17 @@ class UnknownError(CloudError): AWS_EXCEPTIONS = { - 'UserNotFoundException': UserNotFound, - 'NotAuthorizedException': Unauthenticated, - 'UserNotConfirmedException': UserNotConfirmed, - 'PasswordResetRequiredException': PasswordChangeRequired, + "UserNotFoundException": UserNotFound, + "NotAuthorizedException": Unauthenticated, + "UserNotConfirmedException": UserNotConfirmed, + "PasswordResetRequiredException": PasswordChangeRequired, } def _map_aws_exception(err): """Map AWS exception to our exceptions.""" - ex = AWS_EXCEPTIONS.get(err.response['Error']['Code'], UnknownError) - return ex(err.response['Error']['Message']) + ex = AWS_EXCEPTIONS.get(err.response["Error"]["Code"], UnknownError) + return ex(err.response["Error"]["Message"]) def register(cloud, email, password): @@ -67,8 +67,7 @@ def resend_email_confirm(cloud, email): try: cognito.client.resend_confirmation_code( - Username=email, - ClientId=cognito.client_id + Username=email, ClientId=cognito.client_id ) except ClientError as err: raise _map_aws_exception(err) @@ -100,9 +99,8 @@ def check_token(cloud): from botocore.exceptions import ClientError cognito = _cognito( - cloud, - access_token=cloud.access_token, - refresh_token=cloud.refresh_token) + cloud, access_token=cloud.access_token, refresh_token=cloud.refresh_token + ) try: if cognito.check_token(): @@ -118,7 +116,7 @@ def _authenticate(cloud, email, password): from botocore.exceptions import ClientError from warrant.exceptions import ForceChangePasswordException - assert not cloud.is_logged_in, 'Cannot login if already logged in.' + assert not cloud.is_logged_in, "Cannot login if already logged in." cognito = _cognito(cloud, username=email) @@ -146,10 +144,8 @@ def _cognito(cloud, **kwargs): **kwargs ) cognito.client = boto3.client( - 'cognito-idp', + "cognito-idp", region_name=cloud.region, - config=botocore.config.Config( - signature_version=botocore.UNSIGNED - ) + config=botocore.config.Config(signature_version=botocore.UNSIGNED), ) return cognito diff --git a/homeassistant/components/cloud/const.py b/homeassistant/components/cloud/const.py index 82128206d..69fa89b08 100644 --- a/homeassistant/components/cloud/const.py +++ b/homeassistant/components/cloud/const.py @@ -1,16 +1,18 @@ """Constants for the cloud component.""" -DOMAIN = 'cloud' -CONFIG_DIR = '.cloud' +DOMAIN = "cloud" +CONFIG_DIR = ".cloud" REQUEST_TIMEOUT = 10 SERVERS = { - 'production': { - 'cognito_client_id': '60i2uvhvbiref2mftj7rgcrt9u', - 'user_pool_id': 'us-east-1_87ll5WOP8', - 'region': 'us-east-1', - 'relayer': 'wss://cloud.hass.io:8000/websocket', - 'google_actions_sync_url': ('https://24ab3v80xd.execute-api.us-east-1.' - 'amazonaws.com/prod/smart_home_sync'), + "production": { + "cognito_client_id": "60i2uvhvbiref2mftj7rgcrt9u", + "user_pool_id": "us-east-1_87ll5WOP8", + "region": "us-east-1", + "relayer": "wss://cloud.hass.io:8000/websocket", + "google_actions_sync_url": ( + "https://24ab3v80xd.execute-api.us-east-1." + "amazonaws.com/prod/smart_home_sync" + ), } } diff --git a/homeassistant/components/cloud/http_api.py b/homeassistant/components/cloud/http_api.py index a4b3b59f3..b48de7a4d 100644 --- a/homeassistant/components/cloud/http_api.py +++ b/homeassistant/components/cloud/http_api.py @@ -7,8 +7,7 @@ import async_timeout import voluptuous as vol from homeassistant.components.http import HomeAssistantView -from homeassistant.components.http.data_validator import ( - RequestDataValidator) +from homeassistant.components.http.data_validator import RequestDataValidator from . import auth_api from .const import DOMAIN, REQUEST_TIMEOUT @@ -29,15 +28,16 @@ async def async_setup(hass): _CLOUD_ERRORS = { auth_api.UserNotFound: (400, "User does not exist."), - auth_api.UserNotConfirmed: (400, 'Email not confirmed.'), - auth_api.Unauthenticated: (401, 'Authentication failed.'), - auth_api.PasswordChangeRequired: (400, 'Password change required.'), - asyncio.TimeoutError: (502, 'Unable to reach the Home Assistant cloud.') + auth_api.UserNotConfirmed: (400, "Email not confirmed."), + auth_api.Unauthenticated: (401, "Authentication failed."), + auth_api.PasswordChangeRequired: (400, "Password change required."), + asyncio.TimeoutError: (502, "Unable to reach the Home Assistant cloud."), } def _handle_cloud_errors(handler): """Handle auth errors.""" + @wraps(handler) async def error_handler(view, request, *args, **kwargs): """Handle exceptions that raise from the wrapped request handler.""" @@ -48,10 +48,11 @@ def _handle_cloud_errors(handler): except (auth_api.CloudError, asyncio.TimeoutError) as err: err_info = _CLOUD_ERRORS.get(err.__class__) if err_info is None: - err_info = (502, 'Unexpected error: {}'.format(err)) + err_info = (502, "Unexpected error: {}".format(err)) status, msg = err_info - return view.json_message(msg, status_code=status, - message_code=err.__class__.__name__) + return view.json_message( + msg, status_code=status, message_code=err.__class__.__name__ + ) return error_handler @@ -59,13 +60,13 @@ def _handle_cloud_errors(handler): class GoogleActionsSyncView(HomeAssistantView): """Trigger a Google Actions Smart Home Sync.""" - url = '/api/cloud/google_actions/sync' - name = 'api:cloud:google_actions/sync' + url = "/api/cloud/google_actions/sync" + name = "api:cloud:google_actions/sync" @_handle_cloud_errors async def post(self, request): """Trigger a Google Actions sync.""" - hass = request.app['hass'] + hass = request.app["hass"] cloud = hass.data[DOMAIN] websession = hass.helpers.aiohttp_client.async_get_clientsession() @@ -74,9 +75,8 @@ class GoogleActionsSyncView(HomeAssistantView): with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop): req = await websession.post( - cloud.google_actions_sync_url, headers={ - 'authorization': cloud.id_token - }) + cloud.google_actions_sync_url, headers={"authorization": cloud.id_token} + ) return self.json({}, status_code=req.status) @@ -84,22 +84,22 @@ class GoogleActionsSyncView(HomeAssistantView): class CloudLoginView(HomeAssistantView): """Login to Home Assistant cloud.""" - url = '/api/cloud/login' - name = 'api:cloud:login' + url = "/api/cloud/login" + name = "api:cloud:login" @_handle_cloud_errors - @RequestDataValidator(vol.Schema({ - vol.Required('email'): str, - vol.Required('password'): str, - })) + @RequestDataValidator( + vol.Schema({vol.Required("email"): str, vol.Required("password"): str}) + ) async def post(self, request, data): """Handle login request.""" - hass = request.app['hass'] + hass = request.app["hass"] cloud = hass.data[DOMAIN] with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop): - await hass.async_add_job(auth_api.login, cloud, data['email'], - data['password']) + await hass.async_add_job( + auth_api.login, cloud, data["email"], data["password"] + ) hass.async_add_job(cloud.iot.connect) # Allow cloud to start connecting. @@ -110,34 +110,34 @@ class CloudLoginView(HomeAssistantView): class CloudLogoutView(HomeAssistantView): """Log out of the Home Assistant cloud.""" - url = '/api/cloud/logout' - name = 'api:cloud:logout' + url = "/api/cloud/logout" + name = "api:cloud:logout" @_handle_cloud_errors async def post(self, request): """Handle logout request.""" - hass = request.app['hass'] + hass = request.app["hass"] cloud = hass.data[DOMAIN] with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop): await cloud.logout() - return self.json_message('ok') + return self.json_message("ok") class CloudAccountView(HomeAssistantView): """View to retrieve account info.""" - url = '/api/cloud/account' - name = 'api:cloud:account' + url = "/api/cloud/account" + name = "api:cloud:account" async def get(self, request): """Get account info.""" - hass = request.app['hass'] + hass = request.app["hass"] cloud = hass.data[DOMAIN] if not cloud.is_logged_in: - return self.json_message('Not logged in', 400) + return self.json_message("Not logged in", 400) return self.json(_account_data(cloud)) @@ -145,68 +145,69 @@ class CloudAccountView(HomeAssistantView): class CloudRegisterView(HomeAssistantView): """Register on the Home Assistant cloud.""" - url = '/api/cloud/register' - name = 'api:cloud:register' + url = "/api/cloud/register" + name = "api:cloud:register" @_handle_cloud_errors - @RequestDataValidator(vol.Schema({ - vol.Required('email'): str, - vol.Required('password'): vol.All(str, vol.Length(min=6)), - })) + @RequestDataValidator( + vol.Schema( + { + vol.Required("email"): str, + vol.Required("password"): vol.All(str, vol.Length(min=6)), + } + ) + ) async def post(self, request, data): """Handle registration request.""" - hass = request.app['hass'] + hass = request.app["hass"] cloud = hass.data[DOMAIN] with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop): await hass.async_add_job( - auth_api.register, cloud, data['email'], data['password']) + auth_api.register, cloud, data["email"], data["password"] + ) - return self.json_message('ok') + return self.json_message("ok") class CloudResendConfirmView(HomeAssistantView): """Resend email confirmation code.""" - url = '/api/cloud/resend_confirm' - name = 'api:cloud:resend_confirm' + url = "/api/cloud/resend_confirm" + name = "api:cloud:resend_confirm" @_handle_cloud_errors - @RequestDataValidator(vol.Schema({ - vol.Required('email'): str, - })) + @RequestDataValidator(vol.Schema({vol.Required("email"): str})) async def post(self, request, data): """Handle resending confirm email code request.""" - hass = request.app['hass'] + hass = request.app["hass"] cloud = hass.data[DOMAIN] with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop): await hass.async_add_job( - auth_api.resend_email_confirm, cloud, data['email']) + auth_api.resend_email_confirm, cloud, data["email"] + ) - return self.json_message('ok') + return self.json_message("ok") class CloudForgotPasswordView(HomeAssistantView): """View to start Forgot Password flow..""" - url = '/api/cloud/forgot_password' - name = 'api:cloud:forgot_password' + url = "/api/cloud/forgot_password" + name = "api:cloud:forgot_password" @_handle_cloud_errors - @RequestDataValidator(vol.Schema({ - vol.Required('email'): str, - })) + @RequestDataValidator(vol.Schema({vol.Required("email"): str})) async def post(self, request, data): """Handle forgot password request.""" - hass = request.app['hass'] + hass = request.app["hass"] cloud = hass.data[DOMAIN] with async_timeout.timeout(REQUEST_TIMEOUT, loop=hass.loop): - await hass.async_add_job( - auth_api.forgot_password, cloud, data['email']) + await hass.async_add_job(auth_api.forgot_password, cloud, data["email"]) - return self.json_message('ok') + return self.json_message("ok") def _account_data(cloud): @@ -214,7 +215,7 @@ def _account_data(cloud): claims = cloud.claims return { - 'email': claims['email'], - 'sub_exp': claims['custom:sub-exp'], - 'cloud': cloud.iot.state, + "email": claims["email"], + "sub_exp": claims["custom:sub-exp"], + "cloud": cloud.iot.state, } diff --git a/homeassistant/components/cloud/iot.py b/homeassistant/components/cloud/iot.py index f4ce7bb3d..c461d5bf6 100644 --- a/homeassistant/components/cloud/iot.py +++ b/homeassistant/components/cloud/iot.py @@ -16,9 +16,9 @@ from .const import MESSAGE_EXPIRATION, MESSAGE_AUTH_FAIL HANDLERS = Registry() _LOGGER = logging.getLogger(__name__) -STATE_CONNECTING = 'connecting' -STATE_CONNECTED = 'connected' -STATE_DISCONNECTED = 'disconnected' +STATE_CONNECTING = "connecting" +STATE_CONNECTED = "connected" +STATE_DISCONNECTED = "disconnected" class UnknownHandler(Exception): @@ -46,7 +46,7 @@ class CloudIoT: def connect(self): """Connect to the IoT broker.""" if self.state != STATE_DISCONNECTED: - raise RuntimeError('Connect called while not disconnected') + raise RuntimeError("Connect called while not disconnected") hass = self.cloud.hass self.close_requested = False @@ -61,7 +61,8 @@ class CloudIoT: yield from self.disconnect() remove_hass_stop_listener = hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_STOP, _handle_hass_stop) + EVENT_HOMEASSISTANT_STOP, _handle_hass_stop + ) while True: try: @@ -79,8 +80,9 @@ class CloudIoT: try: # Sleep 2^tries seconds between retries - self.retry_task = hass.async_add_job(asyncio.sleep( - 2**min(9, self.tries), loop=hass.loop)) + self.retry_task = hass.async_add_job( + asyncio.sleep(2 ** min(9, self.tries), loop=hass.loop) + ) yield from self.retry_task self.retry_task = None except asyncio.CancelledError: @@ -99,11 +101,11 @@ class CloudIoT: try: yield from hass.async_add_job(auth_api.check_token, self.cloud) except auth_api.Unauthenticated as err: - _LOGGER.error('Unable to refresh token: %s', err) + _LOGGER.error("Unable to refresh token: %s", err) hass.components.persistent_notification.async_create( - MESSAGE_AUTH_FAIL, 'Home Assistant Cloud', - 'cloud_subscription_expired') + MESSAGE_AUTH_FAIL, "Home Assistant Cloud", "cloud_subscription_expired" + ) # Don't await it because it will cancel this task hass.async_add_job(self.cloud.logout()) @@ -114,8 +116,8 @@ class CloudIoT: if self.cloud.subscription_expired: hass.components.persistent_notification.async_create( - MESSAGE_EXPIRATION, 'Home Assistant Cloud', - 'cloud_subscription_expired') + MESSAGE_EXPIRATION, "Home Assistant Cloud", "cloud_subscription_expired" + ) self.close_requested = True return @@ -125,10 +127,10 @@ class CloudIoT: try: self.client = client = yield from session.ws_connect( - self.cloud.relayer, heartbeat=55, headers={ - hdrs.AUTHORIZATION: - 'Bearer {}'.format(self.cloud.id_token) - }) + self.cloud.relayer, + heartbeat=55, + headers={hdrs.AUTHORIZATION: "Bearer {}".format(self.cloud.id_token)}, + ) self.tries = 0 _LOGGER.info("Connected") @@ -141,52 +143,48 @@ class CloudIoT: break elif msg.type == WSMsgType.ERROR: - disconnect_warn = 'Connection error' + disconnect_warn = "Connection error" break elif msg.type != WSMsgType.TEXT: - disconnect_warn = 'Received non-Text message: {}'.format( - msg.type) + disconnect_warn = "Received non-Text message: {}".format(msg.type) break try: msg = msg.json() except ValueError: - disconnect_warn = 'Received invalid JSON.' + disconnect_warn = "Received invalid JSON." break if _LOGGER.isEnabledFor(logging.DEBUG): - _LOGGER.debug("Received message:\n%s\n", - pprint.pformat(msg)) + _LOGGER.debug("Received message:\n%s\n", pprint.pformat(msg)) - response = { - 'msgid': msg['msgid'], - } + response = {"msgid": msg["msgid"]} try: result = yield from async_handle_message( - hass, self.cloud, msg['handler'], msg['payload']) + hass, self.cloud, msg["handler"], msg["payload"] + ) # No response from handler if result is None: continue - response['payload'] = result + response["payload"] = result except UnknownHandler: - response['error'] = 'unknown-handler' + response["error"] = "unknown-handler" except Exception: # pylint: disable=broad-except _LOGGER.exception("Error handling message") - response['error'] = 'exception' + response["error"] = "exception" if _LOGGER.isEnabledFor(logging.DEBUG): - _LOGGER.debug("Publishing message:\n%s\n", - pprint.pformat(response)) + _LOGGER.debug("Publishing message:\n%s\n", pprint.pformat(response)) yield from client.send_json(response) except client_exceptions.WSServerHandshakeError as err: if err.status == 401: - disconnect_warn = 'Invalid auth.' + disconnect_warn = "Invalid auth." self.close_requested = True # Should we notify user? else: @@ -223,33 +221,32 @@ def async_handle_message(hass, cloud, handler_name, payload): return (yield from handler(hass, cloud, payload)) -@HANDLERS.register('alexa') +@HANDLERS.register("alexa") @asyncio.coroutine def async_handle_alexa(hass, cloud, payload): """Handle an incoming IoT message for Alexa.""" - result = yield from alexa.async_handle_message( - hass, cloud.alexa_config, payload) + result = yield from alexa.async_handle_message(hass, cloud.alexa_config, payload) return result -@HANDLERS.register('google_actions') +@HANDLERS.register("google_actions") @asyncio.coroutine def async_handle_google_actions(hass, cloud, payload): """Handle an incoming IoT message for Google Actions.""" - result = yield from ga.async_handle_message( - hass, cloud.gactions_config, payload) + result = yield from ga.async_handle_message(hass, cloud.gactions_config, payload) return result -@HANDLERS.register('cloud') +@HANDLERS.register("cloud") @asyncio.coroutine def async_handle_cloud(hass, cloud, payload): """Handle an incoming IoT message for cloud component.""" - action = payload['action'] + action = payload["action"] - if action == 'logout': + if action == "logout": yield from cloud.logout() - _LOGGER.error("You have been logged out from Home Assistant cloud: %s", - payload['reason']) + _LOGGER.error( + "You have been logged out from Home Assistant cloud: %s", payload["reason"] + ) else: _LOGGER.warning("Received unknown cloud action: %s", action) diff --git a/homeassistant/components/cloudflare.py b/homeassistant/components/cloudflare.py index ae400ca63..71f4d67a3 100644 --- a/homeassistant/components/cloudflare.py +++ b/homeassistant/components/cloudflare.py @@ -13,24 +13,29 @@ from homeassistant.const import CONF_API_KEY, CONF_EMAIL, CONF_ZONE import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import track_time_interval -REQUIREMENTS = ['pycfdns==0.0.1'] +REQUIREMENTS = ["pycfdns==0.0.1"] _LOGGER = logging.getLogger(__name__) -CONF_RECORDS = 'records' +CONF_RECORDS = "records" -DOMAIN = 'cloudflare' +DOMAIN = "cloudflare" INTERVAL = timedelta(minutes=60) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_EMAIL): cv.string, - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_ZONE): cv.string, - vol.Required(CONF_RECORDS): vol.All(cv.ensure_list, [cv.string]), - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_EMAIL): cv.string, + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_ZONE): cv.string, + vol.Required(CONF_RECORDS): vol.All(cv.ensure_list, [cv.string]), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -52,8 +57,7 @@ def setup(hass, config): _update_cloudflare(cfupdate, email, key, zone, records) track_time_interval(hass, update_records_interval, INTERVAL) - hass.services.register( - DOMAIN, 'update_records', update_records_service) + hass.services.register(DOMAIN, "update_records", update_records_service) return True diff --git a/homeassistant/components/coinbase.py b/homeassistant/components/coinbase.py index 154320b4a..0a81851e1 100644 --- a/homeassistant/components/coinbase.py +++ b/homeassistant/components/coinbase.py @@ -14,27 +14,33 @@ import homeassistant.helpers.config_validation as cv from homeassistant.helpers.discovery import load_platform from homeassistant.util import Throttle -REQUIREMENTS = ['coinbase==2.1.0'] +REQUIREMENTS = ["coinbase==2.1.0"] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'coinbase' +DOMAIN = "coinbase" -CONF_API_SECRET = 'api_secret' -CONF_EXCHANGE_CURRENCIES = 'exchange_rate_currencies' +CONF_API_SECRET = "api_secret" +CONF_EXCHANGE_CURRENCIES = "exchange_rate_currencies" MIN_TIME_BETWEEN_UPDATES = timedelta(minutes=1) -DATA_COINBASE = 'coinbase_cache' +DATA_COINBASE = "coinbase_cache" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_API_KEY): cv.string, - vol.Required(CONF_API_SECRET): cv.string, - vol.Optional(CONF_EXCHANGE_CURRENCIES, default=[]): - vol.All(cv.ensure_list, [cv.string]) - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_API_KEY): cv.string, + vol.Required(CONF_API_SECRET): cv.string, + vol.Optional(CONF_EXCHANGE_CURRENCIES, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -47,24 +53,24 @@ def setup(hass, config): api_secret = config[DOMAIN].get(CONF_API_SECRET) exchange_currencies = config[DOMAIN].get(CONF_EXCHANGE_CURRENCIES) - hass.data[DATA_COINBASE] = coinbase_data = CoinbaseData( - api_key, api_secret) + hass.data[DATA_COINBASE] = coinbase_data = CoinbaseData(api_key, api_secret) - if not hasattr(coinbase_data, 'accounts'): + if not hasattr(coinbase_data, "accounts"): return False for account in coinbase_data.accounts.data: - load_platform(hass, 'sensor', DOMAIN, {'account': account}, config) + load_platform(hass, "sensor", DOMAIN, {"account": account}, config) for currency in exchange_currencies: if currency not in coinbase_data.exchange_rates.rates: _LOGGER.warning("Currency %s not found", currency) continue native = coinbase_data.exchange_rates.currency - load_platform(hass, - 'sensor', - DOMAIN, - {'native_currency': native, - 'exchange_currency': currency}, - config) + load_platform( + hass, + "sensor", + DOMAIN, + {"native_currency": native, "exchange_currency": currency}, + config, + ) return True @@ -75,6 +81,7 @@ class CoinbaseData: def __init__(self, api_key, api_secret): """Init the coinbase data object.""" from coinbase.wallet.client import Client + self.client = Client(api_key, api_secret) self.update() @@ -82,9 +89,11 @@ class CoinbaseData: def update(self): """Get the latest data from coinbase.""" from coinbase.wallet.error import AuthenticationError + try: self.accounts = self.client.get_accounts() self.exchange_rates = self.client.get_exchange_rates() except AuthenticationError as coinbase_error: - _LOGGER.error("Authentication error connecting" - " to coinbase: %s", coinbase_error) + _LOGGER.error( + "Authentication error connecting" " to coinbase: %s", coinbase_error + ) diff --git a/homeassistant/components/comfoconnect.py b/homeassistant/components/comfoconnect.py index 69d88274f..10c3723a8 100644 --- a/homeassistant/components/comfoconnect.py +++ b/homeassistant/components/comfoconnect.py @@ -9,50 +9,61 @@ import logging import voluptuous as vol from homeassistant.const import ( - CONF_HOST, CONF_NAME, CONF_PIN, CONF_TOKEN, EVENT_HOMEASSISTANT_STOP) + CONF_HOST, + CONF_NAME, + CONF_PIN, + CONF_TOKEN, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.helpers import discovery import homeassistant.helpers.config_validation as cv from homeassistant.helpers.dispatcher import dispatcher_send -REQUIREMENTS = ['pycomfoconnect==0.3'] +REQUIREMENTS = ["pycomfoconnect==0.3"] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'comfoconnect' +DOMAIN = "comfoconnect" -SIGNAL_COMFOCONNECT_UPDATE_RECEIVED = 'comfoconnect_update_received' +SIGNAL_COMFOCONNECT_UPDATE_RECEIVED = "comfoconnect_update_received" -ATTR_CURRENT_TEMPERATURE = 'current_temperature' -ATTR_CURRENT_HUMIDITY = 'current_humidity' -ATTR_OUTSIDE_TEMPERATURE = 'outside_temperature' -ATTR_OUTSIDE_HUMIDITY = 'outside_humidity' -ATTR_AIR_FLOW_SUPPLY = 'air_flow_supply' -ATTR_AIR_FLOW_EXHAUST = 'air_flow_exhaust' +ATTR_CURRENT_TEMPERATURE = "current_temperature" +ATTR_CURRENT_HUMIDITY = "current_humidity" +ATTR_OUTSIDE_TEMPERATURE = "outside_temperature" +ATTR_OUTSIDE_HUMIDITY = "outside_humidity" +ATTR_AIR_FLOW_SUPPLY = "air_flow_supply" +ATTR_AIR_FLOW_EXHAUST = "air_flow_exhaust" -CONF_USER_AGENT = 'user_agent' +CONF_USER_AGENT = "user_agent" -DEFAULT_NAME = 'ComfoAirQ' +DEFAULT_NAME = "ComfoAirQ" DEFAULT_PIN = 0 -DEFAULT_TOKEN = '00000000000000000000000000000001' -DEFAULT_USER_AGENT = 'Home Assistant' +DEFAULT_TOKEN = "00000000000000000000000000000001" +DEFAULT_USER_AGENT = "Home Assistant" DEVICE = None -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_TOKEN, default=DEFAULT_TOKEN): - vol.Length(min=32, max=32, msg='invalid token'), - vol.Optional(CONF_USER_AGENT, default=DEFAULT_USER_AGENT): cv.string, - vol.Optional(CONF_PIN, default=DEFAULT_PIN): cv.positive_int, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_TOKEN, default=DEFAULT_TOKEN): vol.Length( + min=32, max=32, msg="invalid token" + ), + vol.Optional(CONF_USER_AGENT, default=DEFAULT_USER_AGENT): cv.string, + vol.Optional(CONF_PIN, default=DEFAULT_PIN): cv.positive_int, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): """Set up the ComfoConnect bridge.""" - from pycomfoconnect import (Bridge) + from pycomfoconnect import Bridge conf = config[DOMAIN] host = conf.get(CONF_HOST) @@ -83,7 +94,7 @@ def setup(hass, config): hass.bus.listen_once(EVENT_HOMEASSISTANT_STOP, _shutdown) # Load platforms - discovery.load_platform(hass, 'fan', DOMAIN, {}, config) + discovery.load_platform(hass, "fan", DOMAIN, {}, config) return True @@ -93,15 +104,18 @@ class ComfoConnectBridge: def __init__(self, hass, bridge, name, token, friendly_name, pin): """Initialize the ComfoConnect bridge.""" - from pycomfoconnect import (ComfoConnect) + from pycomfoconnect import ComfoConnect self.data = {} self.name = name self.hass = hass self.comfoconnect = ComfoConnect( - bridge=bridge, local_uuid=bytes.fromhex(token), - local_devicename=friendly_name, pin=pin) + bridge=bridge, + local_uuid=bytes.fromhex(token), + local_devicename=friendly_name, + pin=pin, + ) self.comfoconnect.callback_sensor = self.sensor_callback def connect(self): @@ -119,7 +133,9 @@ class ComfoConnectBridge: _LOGGER.debug("Got value from bridge: %d = %d", var, value) from pycomfoconnect import ( - SENSOR_TEMPERATURE_EXTRACT, SENSOR_TEMPERATURE_OUTDOOR) + SENSOR_TEMPERATURE_EXTRACT, + SENSOR_TEMPERATURE_OUTDOOR, + ) if var in [SENSOR_TEMPERATURE_EXTRACT, SENSOR_TEMPERATURE_OUTDOOR]: self.data[var] = value / 10 diff --git a/homeassistant/components/config/__init__.py b/homeassistant/components/config/__init__.py index df0e2f13a..bc5a5e7ef 100644 --- a/homeassistant/components/config/__init__.py +++ b/homeassistant/components/config/__init__.py @@ -6,36 +6,35 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.const import EVENT_COMPONENT_LOADED, CONF_ID -from homeassistant.setup import ( - async_prepare_setup_platform, ATTR_COMPONENT) +from homeassistant.setup import async_prepare_setup_platform, ATTR_COMPONENT from homeassistant.components.http import HomeAssistantView from homeassistant.util.yaml import load_yaml, dump -DOMAIN = 'config' -DEPENDENCIES = ['http'] +DOMAIN = "config" +DEPENDENCIES = ["http"] SECTIONS = ( - 'automation', - 'config_entries', - 'core', - 'customize', - 'device_registry', - 'entity_registry', - 'group', - 'hassbian', - 'script', + "automation", + "config_entries", + "core", + "customize", + "device_registry", + "entity_registry", + "group", + "hassbian", + "script", ) -ON_DEMAND = ('zwave',) +ON_DEMAND = ("zwave",) async def async_setup(hass, config): """Set up the config component.""" await hass.components.frontend.async_register_built_in_panel( - 'config', 'config', 'hass:settings') + "config", "config", "hass:settings" + ) async def setup_panel(panel_name): """Set up a panel.""" - panel = await async_prepare_setup_platform( - hass, config, DOMAIN, panel_name) + panel = await async_prepare_setup_platform(hass, config, DOMAIN, panel_name) if not panel: return @@ -43,7 +42,7 @@ async def async_setup(hass, config): success = await panel.async_setup(hass) if success: - key = '{}.{}'.format(DOMAIN, panel_name) + key = "{}.{}".format(DOMAIN, panel_name) hass.bus.async_fire(EVENT_COMPONENT_LOADED, {ATTR_COMPONENT: key}) hass.config.components.add(key) @@ -59,8 +58,8 @@ async def async_setup(hass, config): tasks = [setup_panel(panel_name) for panel_name in SECTIONS] if hass.auth.active: - tasks.append(setup_panel('auth')) - tasks.append(setup_panel('auth_provider_homeassistant')) + tasks.append(setup_panel("auth")) + tasks.append(setup_panel("auth_provider_homeassistant")) for panel_name in ON_DEMAND: if panel_name in hass.config.components: @@ -75,11 +74,19 @@ async def async_setup(hass, config): class BaseEditConfigView(HomeAssistantView): """Configure a Group endpoint.""" - def __init__(self, component, config_type, path, key_schema, data_schema, - *, post_write_hook=None): + def __init__( + self, + component, + config_type, + path, + key_schema, + data_schema, + *, + post_write_hook=None + ): """Initialize a config view.""" - self.url = '/api/config/%s/%s/{config_key}' % (component, config_type) - self.name = 'api:config:%s:%s' % (component, config_type) + self.url = "/api/config/%s/%s/{config_key}" % (component, config_type) + self.name = "api:config:%s:%s" % (component, config_type) self.path = path self.key_schema = key_schema self.data_schema = data_schema @@ -99,12 +106,12 @@ class BaseEditConfigView(HomeAssistantView): async def get(self, request, config_key): """Fetch device specific config.""" - hass = request.app['hass'] + hass = request.app["hass"] current = await self.read_config(hass) value = self._get_value(hass, current, config_key) if value is None: - return self.json_message('Resource not found', 404) + return self.json_message("Resource not found", 404) return self.json(value) @@ -113,21 +120,21 @@ class BaseEditConfigView(HomeAssistantView): try: data = await request.json() except ValueError: - return self.json_message('Invalid JSON specified', 400) + return self.json_message("Invalid JSON specified", 400) try: self.key_schema(config_key) except vol.Invalid as err: - return self.json_message('Key malformed: {}'.format(err), 400) + return self.json_message("Key malformed: {}".format(err), 400) try: # We just validate, we don't store that data because # we don't want to store the defaults. self.data_schema(data) except vol.Invalid as err: - return self.json_message('Message malformed: {}'.format(err), 400) + return self.json_message("Message malformed: {}".format(err), 400) - hass = request.app['hass'] + hass = request.app["hass"] path = hass.config.path(self.path) current = await self.read_config(hass) @@ -138,14 +145,11 @@ class BaseEditConfigView(HomeAssistantView): if self.post_write_hook is not None: hass.async_add_job(self.post_write_hook(hass)) - return self.json({ - 'result': 'ok', - }) + return self.json({"result": "ok"}) async def read_config(self, hass): """Read the config.""" - current = await hass.async_add_job( - _read, hass.config.path(self.path)) + current = await hass.async_add_job(_read, hass.config.path(self.path)) if not current: current = self._empty_config() return current @@ -176,8 +180,7 @@ class EditIdBasedConfigView(BaseEditConfigView): def _get_value(self, hass, data, config_key): """Get value.""" - return next( - (val for val in data if val.get(CONF_ID) == config_key), None) + return next((val for val in data if val.get(CONF_ID) == config_key), None) def _write_value(self, hass, data, config_key, new_value): """Set value.""" @@ -203,5 +206,5 @@ def _write(path, data): # Do it before opening file. If dump causes error it will now not # truncate the file. data = dump(data) - with open(path, 'w', encoding='utf-8') as outfile: + with open(path, "w", encoding="utf-8") as outfile: outfile.write(data) diff --git a/homeassistant/components/config/auth.py b/homeassistant/components/config/auth.py index 6f00b03de..fe91e9a82 100644 --- a/homeassistant/components/config/auth.py +++ b/homeassistant/components/config/auth.py @@ -5,37 +5,32 @@ from homeassistant.core import callback from homeassistant.components import websocket_api -WS_TYPE_LIST = 'config/auth/list' -SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_LIST, -}) +WS_TYPE_LIST = "config/auth/list" +SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_LIST} +) -WS_TYPE_DELETE = 'config/auth/delete' -SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_DELETE, - vol.Required('user_id'): str, -}) +WS_TYPE_DELETE = "config/auth/delete" +SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_DELETE, vol.Required("user_id"): str} +) -WS_TYPE_CREATE = 'config/auth/create' -SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_CREATE, - vol.Required('name'): str, -}) +WS_TYPE_CREATE = "config/auth/create" +SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_CREATE, vol.Required("name"): str} +) async def async_setup(hass): """Enable the Home Assistant views.""" hass.components.websocket_api.async_register_command( - WS_TYPE_LIST, websocket_list, - SCHEMA_WS_LIST + WS_TYPE_LIST, websocket_list, SCHEMA_WS_LIST ) hass.components.websocket_api.async_register_command( - WS_TYPE_DELETE, websocket_delete, - SCHEMA_WS_DELETE + WS_TYPE_DELETE, websocket_delete, SCHEMA_WS_DELETE ) hass.components.websocket_api.async_register_command( - WS_TYPE_CREATE, websocket_create, - SCHEMA_WS_CREATE + WS_TYPE_CREATE, websocket_create, SCHEMA_WS_CREATE ) return True @@ -44,12 +39,12 @@ async def async_setup(hass): @websocket_api.require_owner def websocket_list(hass, connection, msg): """Return a list of users.""" + async def send_users(): """Send users.""" result = [_user_info(u) for u in await hass.auth.async_get_users()] - connection.send_message_outside( - websocket_api.result_message(msg['id'], result)) + connection.send_message_outside(websocket_api.result_message(msg["id"], result)) hass.async_add_job(send_users()) @@ -58,25 +53,28 @@ def websocket_list(hass, connection, msg): @websocket_api.require_owner def websocket_delete(hass, connection, msg): """Delete a user.""" + async def delete_user(): """Delete user.""" - if msg['user_id'] == connection.request.get('hass_user').id: - connection.send_message_outside(websocket_api.error_message( - msg['id'], 'no_delete_self', - 'Unable to delete your own account')) + if msg["user_id"] == connection.request.get("hass_user").id: + connection.send_message_outside( + websocket_api.error_message( + msg["id"], "no_delete_self", "Unable to delete your own account" + ) + ) return - user = await hass.auth.async_get_user(msg['user_id']) + user = await hass.auth.async_get_user(msg["user_id"]) if not user: - connection.send_message_outside(websocket_api.error_message( - msg['id'], 'not_found', 'User not found')) + connection.send_message_outside( + websocket_api.error_message(msg["id"], "not_found", "User not found") + ) return await hass.auth.async_remove_user(user) - connection.send_message_outside( - websocket_api.result_message(msg['id'])) + connection.send_message_outside(websocket_api.result_message(msg["id"])) hass.async_add_job(delete_user()) @@ -85,14 +83,14 @@ def websocket_delete(hass, connection, msg): @websocket_api.require_owner def websocket_create(hass, connection, msg): """Create a user.""" + async def create_user(): """Create a user.""" - user = await hass.auth.async_create_user(msg['name']) + user = await hass.auth.async_create_user(msg["name"]) connection.send_message_outside( - websocket_api.result_message(msg['id'], { - 'user': _user_info(user) - })) + websocket_api.result_message(msg["id"], {"user": _user_info(user)}) + ) hass.async_add_job(create_user()) @@ -100,14 +98,10 @@ def websocket_create(hass, connection, msg): def _user_info(user): """Format a user.""" return { - 'id': user.id, - 'name': user.name, - 'is_owner': user.is_owner, - 'is_active': user.is_active, - 'system_generated': user.system_generated, - 'credentials': [ - { - 'type': c.auth_provider_type, - } for c in user.credentials - ] + "id": user.id, + "name": user.name, + "is_owner": user.is_owner, + "is_active": user.is_active, + "system_generated": user.system_generated, + "credentials": [{"type": c.auth_provider_type} for c in user.credentials], } diff --git a/homeassistant/components/config/auth_provider_homeassistant.py b/homeassistant/components/config/auth_provider_homeassistant.py index 960e8f5e7..b2810e38c 100644 --- a/homeassistant/components/config/auth_provider_homeassistant.py +++ b/homeassistant/components/config/auth_provider_homeassistant.py @@ -6,41 +6,41 @@ from homeassistant.core import callback from homeassistant.components import websocket_api -WS_TYPE_CREATE = 'config/auth_provider/homeassistant/create' -SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_CREATE, - vol.Required('user_id'): str, - vol.Required('username'): str, - vol.Required('password'): str, -}) +WS_TYPE_CREATE = "config/auth_provider/homeassistant/create" +SCHEMA_WS_CREATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_CREATE, + vol.Required("user_id"): str, + vol.Required("username"): str, + vol.Required("password"): str, + } +) -WS_TYPE_DELETE = 'config/auth_provider/homeassistant/delete' -SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_DELETE, - vol.Required('username'): str, -}) +WS_TYPE_DELETE = "config/auth_provider/homeassistant/delete" +SCHEMA_WS_DELETE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_DELETE, vol.Required("username"): str} +) -WS_TYPE_CHANGE_PASSWORD = 'config/auth_provider/homeassistant/change_password' -SCHEMA_WS_CHANGE_PASSWORD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_CHANGE_PASSWORD, - vol.Required('current_password'): str, - vol.Required('new_password'): str -}) +WS_TYPE_CHANGE_PASSWORD = "config/auth_provider/homeassistant/change_password" +SCHEMA_WS_CHANGE_PASSWORD = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_CHANGE_PASSWORD, + vol.Required("current_password"): str, + vol.Required("new_password"): str, + } +) async def async_setup(hass): """Enable the Home Assistant views.""" hass.components.websocket_api.async_register_command( - WS_TYPE_CREATE, websocket_create, - SCHEMA_WS_CREATE + WS_TYPE_CREATE, websocket_create, SCHEMA_WS_CREATE ) hass.components.websocket_api.async_register_command( - WS_TYPE_DELETE, websocket_delete, - SCHEMA_WS_DELETE + WS_TYPE_DELETE, websocket_delete, SCHEMA_WS_DELETE ) hass.components.websocket_api.async_register_command( - WS_TYPE_CHANGE_PASSWORD, websocket_change_password, - SCHEMA_WS_CHANGE_PASSWORD + WS_TYPE_CHANGE_PASSWORD, websocket_change_password, SCHEMA_WS_CHANGE_PASSWORD ) return True @@ -48,49 +48,59 @@ async def async_setup(hass): def _get_provider(hass): """Get homeassistant auth provider.""" for prv in hass.auth.auth_providers: - if prv.type == 'homeassistant': + if prv.type == "homeassistant": return prv - raise RuntimeError('Provider not found') + raise RuntimeError("Provider not found") @callback @websocket_api.require_owner def websocket_create(hass, connection, msg): """Create credentials and attach to a user.""" + async def create_creds(): """Create credentials.""" provider = _get_provider(hass) await provider.async_initialize() - user = await hass.auth.async_get_user(msg['user_id']) + user = await hass.auth.async_get_user(msg["user_id"]) if user is None: - connection.send_message_outside(websocket_api.error_message( - msg['id'], 'not_found', 'User not found')) + connection.send_message_outside( + websocket_api.error_message(msg["id"], "not_found", "User not found") + ) return if user.system_generated: - connection.send_message_outside(websocket_api.error_message( - msg['id'], 'system_generated', - 'Cannot add credentials to a system generated user.')) + connection.send_message_outside( + websocket_api.error_message( + msg["id"], + "system_generated", + "Cannot add credentials to a system generated user.", + ) + ) return try: await hass.async_add_executor_job( - provider.data.add_auth, msg['username'], msg['password']) + provider.data.add_auth, msg["username"], msg["password"] + ) except auth_ha.InvalidUser: - connection.send_message_outside(websocket_api.error_message( - msg['id'], 'username_exists', 'Username already exists')) + connection.send_message_outside( + websocket_api.error_message( + msg["id"], "username_exists", "Username already exists" + ) + ) return - credentials = await provider.async_get_or_create_credentials({ - 'username': msg['username'] - }) + credentials = await provider.async_get_or_create_credentials( + {"username": msg["username"]} + ) await hass.auth.async_link_user(user, credentials) await provider.data.async_save() - connection.to_write.put_nowait(websocket_api.result_message(msg['id'])) + connection.to_write.put_nowait(websocket_api.result_message(msg["id"])) hass.async_add_job(create_creds()) @@ -99,34 +109,36 @@ def websocket_create(hass, connection, msg): @websocket_api.require_owner def websocket_delete(hass, connection, msg): """Delete username and related credential.""" + async def delete_creds(): """Delete user credentials.""" provider = _get_provider(hass) await provider.async_initialize() - credentials = await provider.async_get_or_create_credentials({ - 'username': msg['username'] - }) + credentials = await provider.async_get_or_create_credentials( + {"username": msg["username"]} + ) # if not new, an existing credential exists. # Removing the credential will also remove the auth. if not credentials.is_new: await hass.auth.async_remove_credentials(credentials) - connection.to_write.put_nowait( - websocket_api.result_message(msg['id'])) + connection.to_write.put_nowait(websocket_api.result_message(msg["id"])) return try: - provider.data.async_remove_auth(msg['username']) + provider.data.async_remove_auth(msg["username"]) await provider.data.async_save() except auth_ha.InvalidUser: - connection.to_write.put_nowait(websocket_api.error_message( - msg['id'], 'auth_not_found', 'Given username was not found.')) + connection.to_write.put_nowait( + websocket_api.error_message( + msg["id"], "auth_not_found", "Given username was not found." + ) + ) return - connection.to_write.put_nowait( - websocket_api.result_message(msg['id'])) + connection.to_write.put_nowait(websocket_api.result_message(msg["id"])) hass.async_add_job(delete_creds()) @@ -134,12 +146,16 @@ def websocket_delete(hass, connection, msg): @callback def websocket_change_password(hass, connection, msg): """Change user password.""" + async def change_password(): """Change user password.""" - user = connection.request.get('hass_user') + user = connection.request.get("hass_user") if user is None: - connection.send_message_outside(websocket_api.error_message( - msg['id'], 'user_not_found', 'User not found')) + connection.send_message_outside( + websocket_api.error_message( + msg["id"], "user_not_found", "User not found" + ) + ) return provider = _get_provider(hass) @@ -148,27 +164,32 @@ def websocket_change_password(hass, connection, msg): username = None for credential in user.credentials: if credential.auth_provider_type == provider.type: - username = credential.data['username'] + username = credential.data["username"] break if username is None: - connection.send_message_outside(websocket_api.error_message( - msg['id'], 'credentials_not_found', 'Credentials not found')) + connection.send_message_outside( + websocket_api.error_message( + msg["id"], "credentials_not_found", "Credentials not found" + ) + ) return try: - await provider.async_validate_login( - username, msg['current_password']) + await provider.async_validate_login(username, msg["current_password"]) except auth_ha.InvalidAuth: - connection.send_message_outside(websocket_api.error_message( - msg['id'], 'invalid_password', 'Invalid password')) + connection.send_message_outside( + websocket_api.error_message( + msg["id"], "invalid_password", "Invalid password" + ) + ) return await hass.async_add_executor_job( - provider.data.change_password, username, msg['new_password']) + provider.data.change_password, username, msg["new_password"] + ) await provider.data.async_save() - connection.send_message_outside( - websocket_api.result_message(msg['id'])) + connection.send_message_outside(websocket_api.result_message(msg["id"])) hass.async_add_job(change_password()) diff --git a/homeassistant/components/config/automation.py b/homeassistant/components/config/automation.py index 223159eb4..004fc775c 100644 --- a/homeassistant/components/config/automation.py +++ b/homeassistant/components/config/automation.py @@ -5,21 +5,26 @@ import uuid from homeassistant.const import CONF_ID from homeassistant.components.config import EditIdBasedConfigView -from homeassistant.components.automation import ( - PLATFORM_SCHEMA, DOMAIN, async_reload) +from homeassistant.components.automation import PLATFORM_SCHEMA, DOMAIN, async_reload import homeassistant.helpers.config_validation as cv -CONFIG_PATH = 'automations.yaml' +CONFIG_PATH = "automations.yaml" @asyncio.coroutine def async_setup(hass): """Set up the Automation config API.""" - hass.http.register_view(EditAutomationConfigView( - DOMAIN, 'config', CONFIG_PATH, cv.string, - PLATFORM_SCHEMA, post_write_hook=async_reload - )) + hass.http.register_view( + EditAutomationConfigView( + DOMAIN, + "config", + CONFIG_PATH, + cv.string, + PLATFORM_SCHEMA, + post_write_hook=async_reload, + ) + ) return True @@ -45,7 +50,7 @@ class EditAutomationConfigView(EditIdBasedConfigView): # Iterate through some keys that we want to have ordered in the output updated_value = OrderedDict() - for key in ('id', 'alias', 'trigger', 'condition', 'action'): + for key in ("id", "alias", "trigger", "condition", "action"): if key in cur_value: updated_value[key] = cur_value[key] if key in new_value: diff --git a/homeassistant/components/config/config_entries.py b/homeassistant/components/config/config_entries.py index e0c0e7daa..c30338de3 100644 --- a/homeassistant/components/config/config_entries.py +++ b/homeassistant/components/config/config_entries.py @@ -4,7 +4,9 @@ import asyncio from homeassistant import config_entries, data_entry_flow from homeassistant.components.http import HomeAssistantView from homeassistant.helpers.data_entry_flow import ( - FlowManagerIndexView, FlowManagerResourceView) + FlowManagerIndexView, + FlowManagerResourceView, +) @asyncio.coroutine @@ -12,28 +14,26 @@ def async_setup(hass): """Enable the Home Assistant views.""" hass.http.register_view(ConfigManagerEntryIndexView) hass.http.register_view(ConfigManagerEntryResourceView) - hass.http.register_view( - ConfigManagerFlowIndexView(hass.config_entries.flow)) - hass.http.register_view( - ConfigManagerFlowResourceView(hass.config_entries.flow)) + hass.http.register_view(ConfigManagerFlowIndexView(hass.config_entries.flow)) + hass.http.register_view(ConfigManagerFlowResourceView(hass.config_entries.flow)) hass.http.register_view(ConfigManagerAvailableFlowView) return True def _prepare_json(result): """Convert result for JSON.""" - if result['type'] != data_entry_flow.RESULT_TYPE_FORM: + if result["type"] != data_entry_flow.RESULT_TYPE_FORM: return result import voluptuous_serialize data = result.copy() - schema = data['data_schema'] + schema = data["data_schema"] if schema is None: - data['data_schema'] = [] + data["data_schema"] = [] else: - data['data_schema'] = voluptuous_serialize.convert(schema) + data["data_schema"] = voluptuous_serialize.convert(schema) return data @@ -41,37 +41,42 @@ def _prepare_json(result): class ConfigManagerEntryIndexView(HomeAssistantView): """View to get available config entries.""" - url = '/api/config/config_entries/entry' - name = 'api:config:config_entries:entry' + url = "/api/config/config_entries/entry" + name = "api:config:config_entries:entry" @asyncio.coroutine def get(self, request): """List flows in progress.""" - hass = request.app['hass'] - return self.json([{ - 'entry_id': entry.entry_id, - 'domain': entry.domain, - 'title': entry.title, - 'source': entry.source, - 'state': entry.state, - } for entry in hass.config_entries.async_entries()]) + hass = request.app["hass"] + return self.json( + [ + { + "entry_id": entry.entry_id, + "domain": entry.domain, + "title": entry.title, + "source": entry.source, + "state": entry.state, + } + for entry in hass.config_entries.async_entries() + ] + ) class ConfigManagerEntryResourceView(HomeAssistantView): """View to interact with a config entry.""" - url = '/api/config/config_entries/entry/{entry_id}' - name = 'api:config:config_entries:entry:resource' + url = "/api/config/config_entries/entry/{entry_id}" + name = "api:config:config_entries:entry:resource" @asyncio.coroutine def delete(self, request, entry_id): """Delete a config entry.""" - hass = request.app['hass'] + hass = request.app["hass"] try: result = yield from hass.config_entries.async_remove(entry_id) except config_entries.UnknownEntry: - return self.json_message('Invalid entry specified', 404) + return self.json_message("Invalid entry specified", 404) return self.json(result) @@ -79,8 +84,8 @@ class ConfigManagerEntryResourceView(HomeAssistantView): class ConfigManagerFlowIndexView(FlowManagerIndexView): """View to create config flows.""" - url = '/api/config/config_entries/flow' - name = 'api:config:config_entries:flow' + url = "/api/config/config_entries/flow" + name = "api:config:config_entries:flow" @asyncio.coroutine def get(self, request): @@ -89,25 +94,29 @@ class ConfigManagerFlowIndexView(FlowManagerIndexView): Example of a non-user initiated flow is a discovered Hue hub that requires user interaction to finish setup. """ - hass = request.app['hass'] + hass = request.app["hass"] - return self.json([ - flw for flw in hass.config_entries.flow.async_progress() - if flw['context']['source'] != config_entries.SOURCE_USER]) + return self.json( + [ + flw + for flw in hass.config_entries.flow.async_progress() + if flw["context"]["source"] != config_entries.SOURCE_USER + ] + ) class ConfigManagerFlowResourceView(FlowManagerResourceView): """View to interact with the flow manager.""" - url = '/api/config/config_entries/flow/{flow_id}' - name = 'api:config:config_entries:flow:resource' + url = "/api/config/config_entries/flow/{flow_id}" + name = "api:config:config_entries:flow:resource" class ConfigManagerAvailableFlowView(HomeAssistantView): """View to query available flows.""" - url = '/api/config/config_entries/flow_handlers' - name = 'api:config:config_entries:flow_handlers' + url = "/api/config/config_entries/flow_handlers" + name = "api:config:config_entries:flow_handlers" @asyncio.coroutine def get(self, request): diff --git a/homeassistant/components/config/core.py b/homeassistant/components/config/core.py index 4ff530ad2..4e24c6a42 100644 --- a/homeassistant/components/config/core.py +++ b/homeassistant/components/config/core.py @@ -15,17 +15,14 @@ def async_setup(hass): class CheckConfigView(HomeAssistantView): """Hassbian packages endpoint.""" - url = '/api/config/core/check_config' - name = 'api:config:core:check_config' + url = "/api/config/core/check_config" + name = "api:config:core:check_config" @asyncio.coroutine def post(self, request): """Validate configuration and return results.""" - errors = yield from async_check_ha_config_file(request.app['hass']) + errors = yield from async_check_ha_config_file(request.app["hass"]) - state = 'invalid' if errors else 'valid' + state = "invalid" if errors else "valid" - return self.json({ - "result": state, - "errors": errors, - }) + return self.json({"result": state, "errors": errors}) diff --git a/homeassistant/components/config/customize.py b/homeassistant/components/config/customize.py index d25992ecc..eba712e6a 100644 --- a/homeassistant/components/config/customize.py +++ b/homeassistant/components/config/customize.py @@ -7,16 +7,22 @@ from homeassistant.config import DATA_CUSTOMIZE import homeassistant.helpers.config_validation as cv -CONFIG_PATH = 'customize.yaml' +CONFIG_PATH = "customize.yaml" @asyncio.coroutine def async_setup(hass): """Set up the Customize config API.""" - hass.http.register_view(CustomizeConfigView( - 'customize', 'config', CONFIG_PATH, cv.entity_id, dict, - post_write_hook=async_reload_core_config - )) + hass.http.register_view( + CustomizeConfigView( + "customize", + "config", + CONFIG_PATH, + cv.entity_id, + dict, + post_write_hook=async_reload_core_config, + ) + ) return True @@ -27,7 +33,7 @@ class CustomizeConfigView(EditKeyBasedConfigView): def _get_value(self, hass, data, config_key): """Get value.""" customize = hass.data.get(DATA_CUSTOMIZE, {}).get(config_key) or {} - return {'global': customize, 'local': data.get(config_key, {})} + return {"global": customize, "local": data.get(config_key, {})} def _write_value(self, hass, data, config_key, new_value): """Set value.""" diff --git a/homeassistant/components/config/device_registry.py b/homeassistant/components/config/device_registry.py index 8383e0cdc..02f2b3331 100644 --- a/homeassistant/components/config/device_registry.py +++ b/homeassistant/components/config/device_registry.py @@ -5,19 +5,18 @@ from homeassistant.core import callback from homeassistant.helpers.device_registry import async_get_registry from homeassistant.components import websocket_api -DEPENDENCIES = ['websocket_api'] +DEPENDENCIES = ["websocket_api"] -WS_TYPE_LIST = 'config/device_registry/list' -SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_LIST, -}) +WS_TYPE_LIST = "config/device_registry/list" +SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_LIST} +) async def async_setup(hass): """Enable the Entity Registry views.""" hass.components.websocket_api.async_register_command( - WS_TYPE_LIST, websocket_list_devices, - SCHEMA_WS_LIST + WS_TYPE_LIST, websocket_list_devices, SCHEMA_WS_LIST ) return True @@ -28,19 +27,26 @@ def websocket_list_devices(hass, connection, msg): Async friendly. """ + async def retrieve_entities(): """Get devices from registry.""" registry = await async_get_registry(hass) - connection.send_message_outside(websocket_api.result_message( - msg['id'], [{ - 'config_entries': list(entry.config_entries), - 'connections': list(entry.connections), - 'manufacturer': entry.manufacturer, - 'model': entry.model, - 'name': entry.name, - 'sw_version': entry.sw_version, - 'id': entry.id, - } for entry in registry.devices.values()] - )) + connection.send_message_outside( + websocket_api.result_message( + msg["id"], + [ + { + "config_entries": list(entry.config_entries), + "connections": list(entry.connections), + "manufacturer": entry.manufacturer, + "model": entry.model, + "name": entry.name, + "sw_version": entry.sw_version, + "id": entry.id, + } + for entry in registry.devices.values() + ], + ) + ) hass.async_add_job(retrieve_entities()) diff --git a/homeassistant/components/config/entity_registry.py b/homeassistant/components/config/entity_registry.py index 0f9abf167..7f2e4d4ab 100644 --- a/homeassistant/components/config/entity_registry.py +++ b/homeassistant/components/config/entity_registry.py @@ -6,42 +6,40 @@ from homeassistant.helpers.entity_registry import async_get_registry from homeassistant.components import websocket_api from homeassistant.helpers import config_validation as cv -DEPENDENCIES = ['websocket_api'] +DEPENDENCIES = ["websocket_api"] -WS_TYPE_LIST = 'config/entity_registry/list' -SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_LIST, -}) +WS_TYPE_LIST = "config/entity_registry/list" +SCHEMA_WS_LIST = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_LIST} +) -WS_TYPE_GET = 'config/entity_registry/get' -SCHEMA_WS_GET = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_GET, - vol.Required('entity_id'): cv.entity_id -}) +WS_TYPE_GET = "config/entity_registry/get" +SCHEMA_WS_GET = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + {vol.Required("type"): WS_TYPE_GET, vol.Required("entity_id"): cv.entity_id} +) -WS_TYPE_UPDATE = 'config/entity_registry/update' -SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend({ - vol.Required('type'): WS_TYPE_UPDATE, - vol.Required('entity_id'): cv.entity_id, - # If passed in, we update value. Passing None will remove old value. - vol.Optional('name'): vol.Any(str, None), - vol.Optional('new_entity_id'): str, -}) +WS_TYPE_UPDATE = "config/entity_registry/update" +SCHEMA_WS_UPDATE = websocket_api.BASE_COMMAND_MESSAGE_SCHEMA.extend( + { + vol.Required("type"): WS_TYPE_UPDATE, + vol.Required("entity_id"): cv.entity_id, + # If passed in, we update value. Passing None will remove old value. + vol.Optional("name"): vol.Any(str, None), + vol.Optional("new_entity_id"): str, + } +) async def async_setup(hass): """Enable the Entity Registry views.""" hass.components.websocket_api.async_register_command( - WS_TYPE_LIST, websocket_list_entities, - SCHEMA_WS_LIST + WS_TYPE_LIST, websocket_list_entities, SCHEMA_WS_LIST ) hass.components.websocket_api.async_register_command( - WS_TYPE_GET, websocket_get_entity, - SCHEMA_WS_GET + WS_TYPE_GET, websocket_get_entity, SCHEMA_WS_GET ) hass.components.websocket_api.async_register_command( - WS_TYPE_UPDATE, websocket_update_entity, - SCHEMA_WS_UPDATE + WS_TYPE_UPDATE, websocket_update_entity, SCHEMA_WS_UPDATE ) return True @@ -52,19 +50,26 @@ def websocket_list_entities(hass, connection, msg): Async friendly. """ + async def retrieve_entities(): """Get entities from registry.""" registry = await async_get_registry(hass) - connection.send_message_outside(websocket_api.result_message( - msg['id'], [{ - 'config_entry_id': entry.config_entry_id, - 'device_id': entry.device_id, - 'disabled_by': entry.disabled_by, - 'entity_id': entry.entity_id, - 'name': entry.name, - 'platform': entry.platform, - } for entry in registry.entities.values()] - )) + connection.send_message_outside( + websocket_api.result_message( + msg["id"], + [ + { + "config_entry_id": entry.config_entry_id, + "device_id": entry.device_id, + "disabled_by": entry.disabled_by, + "entity_id": entry.entity_id, + "name": entry.name, + "platform": entry.platform, + } + for entry in registry.entities.values() + ], + ) + ) hass.async_add_job(retrieve_entities()) @@ -75,19 +80,23 @@ def websocket_get_entity(hass, connection, msg): Async friendly. """ + async def retrieve_entity(): """Get entity from registry.""" registry = await async_get_registry(hass) - entry = registry.entities.get(msg['entity_id']) + entry = registry.entities.get(msg["entity_id"]) if entry is None: - connection.send_message_outside(websocket_api.error_message( - msg['id'], websocket_api.ERR_NOT_FOUND, 'Entity not found')) + connection.send_message_outside( + websocket_api.error_message( + msg["id"], websocket_api.ERR_NOT_FOUND, "Entity not found" + ) + ) return - connection.send_message_outside(websocket_api.result_message( - msg['id'], _entry_dict(entry) - )) + connection.send_message_outside( + websocket_api.result_message(msg["id"], _entry_dict(entry)) + ) hass.async_add_job(retrieve_entity()) @@ -98,35 +107,38 @@ def websocket_update_entity(hass, connection, msg): Async friendly. """ + async def update_entity(): """Get entity from registry.""" registry = await async_get_registry(hass) - if msg['entity_id'] not in registry.entities: - connection.send_message_outside(websocket_api.error_message( - msg['id'], websocket_api.ERR_NOT_FOUND, 'Entity not found')) + if msg["entity_id"] not in registry.entities: + connection.send_message_outside( + websocket_api.error_message( + msg["id"], websocket_api.ERR_NOT_FOUND, "Entity not found" + ) + ) return changes = {} - if 'name' in msg: - changes['name'] = msg['name'] + if "name" in msg: + changes["name"] = msg["name"] - if 'new_entity_id' in msg: - changes['new_entity_id'] = msg['new_entity_id'] + if "new_entity_id" in msg: + changes["new_entity_id"] = msg["new_entity_id"] try: if changes: - entry = registry.async_update_entity( - msg['entity_id'], **changes) + entry = registry.async_update_entity(msg["entity_id"], **changes) except ValueError as err: - connection.send_message_outside(websocket_api.error_message( - msg['id'], 'invalid_info', str(err) - )) + connection.send_message_outside( + websocket_api.error_message(msg["id"], "invalid_info", str(err)) + ) else: - connection.send_message_outside(websocket_api.result_message( - msg['id'], _entry_dict(entry) - )) + connection.send_message_outside( + websocket_api.result_message(msg["id"], _entry_dict(entry)) + ) hass.async_create_task(update_entity()) @@ -134,7 +146,4 @@ def websocket_update_entity(hass, connection, msg): @callback def _entry_dict(entry): """Convert entry to API format.""" - return { - 'entity_id': entry.entity_id, - 'name': entry.name - } + return {"entity_id": entry.entity_id, "name": entry.name} diff --git a/homeassistant/components/config/group.py b/homeassistant/components/config/group.py index 8b327faa9..b348f2360 100644 --- a/homeassistant/components/config/group.py +++ b/homeassistant/components/config/group.py @@ -6,19 +6,21 @@ from homeassistant.components.group import DOMAIN, GROUP_SCHEMA import homeassistant.helpers.config_validation as cv -CONFIG_PATH = 'groups.yaml' +CONFIG_PATH = "groups.yaml" @asyncio.coroutine def async_setup(hass): """Set up the Group config API.""" + @asyncio.coroutine def hook(hass): """post_write_hook for Config View that reloads groups.""" yield from hass.services.async_call(DOMAIN, SERVICE_RELOAD) - hass.http.register_view(EditKeyBasedConfigView( - 'group', 'config', CONFIG_PATH, cv.slug, GROUP_SCHEMA, - post_write_hook=hook - )) + hass.http.register_view( + EditKeyBasedConfigView( + "group", "config", CONFIG_PATH, cv.slug, GROUP_SCHEMA, post_write_hook=hook + ) + ) return True diff --git a/homeassistant/components/config/hassbian.py b/homeassistant/components/config/hassbian.py index 8de5f62d9..f1f452ea4 100644 --- a/homeassistant/components/config/hassbian.py +++ b/homeassistant/components/config/hassbian.py @@ -34,7 +34,7 @@ _TEST_OUTPUT = """ def async_setup(hass): """Set up the Hassbian config.""" # Test if is Hassbian - test_mode = 'FORCE_HASSBIAN' in os.environ + test_mode = "FORCE_HASSBIAN" in os.environ is_hassbian = test_mode if not is_hassbian: @@ -53,14 +53,14 @@ def hassbian_status(hass, test_mode=False): if test_mode: return json.loads(_TEST_OUTPUT) - raise Exception('Real mode not implemented yet.') + raise Exception("Real mode not implemented yet.") class HassbianSuitesView(HomeAssistantView): """Hassbian packages endpoint.""" - url = '/api/config/hassbian/suites' - name = 'api:config:hassbian:suites' + url = "/api/config/hassbian/suites" + name = "api:config:hassbian:suites" def __init__(self, test_mode): """Initialize suites view.""" @@ -69,16 +69,16 @@ class HassbianSuitesView(HomeAssistantView): @asyncio.coroutine def get(self, request): """Request suite status.""" - inp = yield from hassbian_status(request.app['hass'], self._test_mode) + inp = yield from hassbian_status(request.app["hass"], self._test_mode) - return self.json(inp['suites']) + return self.json(inp["suites"]) class HassbianSuiteInstallView(HomeAssistantView): """Hassbian packages endpoint.""" - url = '/api/config/hassbian/suites/{suite}/install' - name = 'api:config:hassbian:suite' + url = "/api/config/hassbian/suites/{suite}/install" + name = "api:config:hassbian:suite" def __init__(self, test_mode): """Initialize suite view.""" diff --git a/homeassistant/components/config/script.py b/homeassistant/components/config/script.py index 345c8e4a8..561468323 100644 --- a/homeassistant/components/config/script.py +++ b/homeassistant/components/config/script.py @@ -6,14 +6,20 @@ from homeassistant.components.script import SCRIPT_ENTRY_SCHEMA, async_reload import homeassistant.helpers.config_validation as cv -CONFIG_PATH = 'scripts.yaml' +CONFIG_PATH = "scripts.yaml" @asyncio.coroutine def async_setup(hass): """Set up the script config API.""" - hass.http.register_view(EditKeyBasedConfigView( - 'script', 'config', CONFIG_PATH, cv.slug, SCRIPT_ENTRY_SCHEMA, - post_write_hook=async_reload - )) + hass.http.register_view( + EditKeyBasedConfigView( + "script", + "config", + CONFIG_PATH, + cv.slug, + SCRIPT_ENTRY_SCHEMA, + post_write_hook=async_reload, + ) + ) return True diff --git a/homeassistant/components/config/zwave.py b/homeassistant/components/config/zwave.py index fcdab8350..4710be64c 100644 --- a/homeassistant/components/config/zwave.py +++ b/homeassistant/components/config/zwave.py @@ -12,17 +12,22 @@ from homeassistant.components.zwave import const, DEVICE_CONFIG_SCHEMA_ENTRY import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONFIG_PATH = 'zwave_device_config.yaml' -OZW_LOG_FILENAME = 'OZW_Log.txt' +CONFIG_PATH = "zwave_device_config.yaml" +OZW_LOG_FILENAME = "OZW_Log.txt" @asyncio.coroutine def async_setup(hass): """Set up the Z-Wave config API.""" - hass.http.register_view(EditKeyBasedConfigView( - 'zwave', 'device_config', CONFIG_PATH, cv.entity_id, - DEVICE_CONFIG_SCHEMA_ENTRY - )) + hass.http.register_view( + EditKeyBasedConfigView( + "zwave", + "device_config", + CONFIG_PATH, + cv.entity_id, + DEVICE_CONFIG_SCHEMA_ENTRY, + ) + ) hass.http.register_view(ZWaveNodeValueView) hass.http.register_view(ZWaveNodeGroupView) hass.http.register_view(ZWaveNodeConfigView) @@ -40,24 +45,24 @@ class ZWaveLogView(HomeAssistantView): url = "/api/zwave/ozwlog" name = "api:zwave:ozwlog" -# pylint: disable=no-self-use + # pylint: disable=no-self-use @asyncio.coroutine def get(self, request): """Retrieve the lines from ZWave log.""" try: - lines = int(request.query.get('lines', 0)) + lines = int(request.query.get("lines", 0)) except ValueError: - return Response(text='Invalid datetime', status=400) + return Response(text="Invalid datetime", status=400) - hass = request.app['hass'] + hass = request.app["hass"] response = yield from hass.async_add_job(self._get_log, hass, lines) - return Response(text='\n'.join(response)) + return Response(text="\n".join(response)) def _get_log(self, hass, lines): """Retrieve the logfile content.""" logfilepath = hass.config.path(OZW_LOG_FILENAME) - with open(logfilepath, 'r') as logfile: + with open(logfilepath, "r") as logfile: data = (line.rstrip() for line in logfile) if lines == 0: loglines = list(data) @@ -75,15 +80,13 @@ class ZWaveConfigWriteView(HomeAssistantView): @ha.callback def post(self, request): """Save cache configuration to zwcfg_xxxxx.xml.""" - hass = request.app['hass'] + hass = request.app["hass"] network = hass.data.get(const.DATA_NETWORK) if network is None: - return self.json_message('No Z-Wave network data found', - HTTP_NOT_FOUND) + return self.json_message("No Z-Wave network data found", HTTP_NOT_FOUND) _LOGGER.info("Z-Wave configuration written to file.") network.write_config() - return self.json_message('Z-Wave configuration saved to file.', - HTTP_OK) + return self.json_message("Z-Wave configuration saved to file.", HTTP_OK) class ZWaveNodeValueView(HomeAssistantView): @@ -96,7 +99,7 @@ class ZWaveNodeValueView(HomeAssistantView): def get(self, request, node_id): """Retrieve groups of node.""" nodeid = int(node_id) - hass = request.app['hass'] + hass = request.app["hass"] values_list = hass.data[const.DATA_ENTITY_VALUES] values_data = {} @@ -107,10 +110,10 @@ class ZWaveNodeValueView(HomeAssistantView): continue values_data[entity_values.primary.value_id] = { - 'label': entity_values.primary.label, - 'index': entity_values.primary.index, - 'instance': entity_values.primary.instance, - 'poll_intensity': entity_values.primary.poll_intensity, + "label": entity_values.primary.label, + "index": entity_values.primary.index, + "instance": entity_values.primary.instance, + "poll_intensity": entity_values.primary.poll_intensity, } return self.json(values_data) @@ -125,19 +128,20 @@ class ZWaveNodeGroupView(HomeAssistantView): def get(self, request, node_id): """Retrieve groups of node.""" nodeid = int(node_id) - hass = request.app['hass'] + hass = request.app["hass"] network = hass.data.get(const.DATA_NETWORK) node = network.nodes.get(nodeid) if node is None: - return self.json_message('Node not found', HTTP_NOT_FOUND) + return self.json_message("Node not found", HTTP_NOT_FOUND) groupdata = node.groups groups = {} for key, value in groupdata.items(): - groups[key] = {'associations': value.associations, - 'association_instances': - value.associations_instances, - 'label': value.label, - 'max_associations': value.max_associations} + groups[key] = { + "associations": value.associations, + "association_instances": value.associations_instances, + "label": value.label, + "max_associations": value.max_associations, + } return self.json(groups) @@ -151,22 +155,24 @@ class ZWaveNodeConfigView(HomeAssistantView): def get(self, request, node_id): """Retrieve configurations of node.""" nodeid = int(node_id) - hass = request.app['hass'] + hass = request.app["hass"] network = hass.data.get(const.DATA_NETWORK) node = network.nodes.get(nodeid) if node is None: - return self.json_message('Node not found', HTTP_NOT_FOUND) + return self.json_message("Node not found", HTTP_NOT_FOUND) config = {} - for value in ( - node.get_values(class_id=const.COMMAND_CLASS_CONFIGURATION) - .values()): - config[value.index] = {'label': value.label, - 'type': value.type, - 'help': value.help, - 'data_items': value.data_items, - 'data': value.data, - 'max': value.max, - 'min': value.min} + for value in node.get_values( + class_id=const.COMMAND_CLASS_CONFIGURATION + ).values(): + config[value.index] = { + "label": value.label, + "type": value.type, + "help": value.help, + "data_items": value.data_items, + "data": value.data, + "max": value.max, + "min": value.min, + } return self.json(config) @@ -180,22 +186,22 @@ class ZWaveUserCodeView(HomeAssistantView): def get(self, request, node_id): """Retrieve usercodes of node.""" nodeid = int(node_id) - hass = request.app['hass'] + hass = request.app["hass"] network = hass.data.get(const.DATA_NETWORK) node = network.nodes.get(nodeid) if node is None: - return self.json_message('Node not found', HTTP_NOT_FOUND) + return self.json_message("Node not found", HTTP_NOT_FOUND) usercodes = {} if not node.has_command_class(const.COMMAND_CLASS_USER_CODE): return self.json(usercodes) - for value in ( - node.get_values(class_id=const.COMMAND_CLASS_USER_CODE) - .values()): + for value in node.get_values(class_id=const.COMMAND_CLASS_USER_CODE).values(): if value.genre != const.GENRE_USER: continue - usercodes[value.index] = {'code': value.data, - 'label': value.label, - 'length': len(value.data)} + usercodes[value.index] = { + "code": value.data, + "label": value.label, + "length": len(value.data), + } return self.json(usercodes) @@ -208,22 +214,23 @@ class ZWaveProtectionView(HomeAssistantView): async def get(self, request, node_id): """Retrieve the protection commandclass options of node.""" nodeid = int(node_id) - hass = request.app['hass'] + hass = request.app["hass"] network = hass.data.get(const.DATA_NETWORK) def _fetch_protection(): """Get protection data.""" node = network.nodes.get(nodeid) if node is None: - return self.json_message('Node not found', HTTP_NOT_FOUND) + return self.json_message("Node not found", HTTP_NOT_FOUND) protection_options = {} if not node.has_command_class(const.COMMAND_CLASS_PROTECTION): return self.json(protection_options) protections = node.get_protections() protection_options = { - 'value_id': '{0:d}'.format(list(protections)[0]), - 'selected': node.get_protection_item(list(protections)[0]), - 'options': node.get_protection_items(list(protections)[0])} + "value_id": "{0:d}".format(list(protections)[0]), + "selected": node.get_protection_item(list(protections)[0]), + "options": node.get_protection_items(list(protections)[0]), + } return self.json(protection_options) return await hass.async_add_executor_job(_fetch_protection) @@ -231,7 +238,7 @@ class ZWaveProtectionView(HomeAssistantView): async def post(self, request, node_id): """Change the selected option in protection commandclass.""" nodeid = int(node_id) - hass = request.app['hass'] + hass = request.app["hass"] network = hass.data.get(const.DATA_NETWORK) protection_data = await request.json() @@ -241,15 +248,14 @@ class ZWaveProtectionView(HomeAssistantView): selection = protection_data["selection"] value_id = int(protection_data[const.ATTR_VALUE_ID]) if node is None: - return self.json_message('Node not found', HTTP_NOT_FOUND) + return self.json_message("Node not found", HTTP_NOT_FOUND) if not node.has_command_class(const.COMMAND_CLASS_PROTECTION): return self.json_message( - 'No protection commandclass on this node', HTTP_NOT_FOUND) + "No protection commandclass on this node", HTTP_NOT_FOUND + ) state = node.set_protection(value_id, selection) if not state: - return self.json_message( - 'Protection setting did not complete', 202) - return self.json_message( - 'Protection setting succsessfully set', HTTP_OK) + return self.json_message("Protection setting did not complete", 202) + return self.json_message("Protection setting succsessfully set", HTTP_OK) return await hass.async_add_executor_job(_set_protection) diff --git a/homeassistant/components/configurator.py b/homeassistant/components/configurator.py index 56fb7b424..2c7b57c8a 100644 --- a/homeassistant/components/configurator.py +++ b/homeassistant/components/configurator.py @@ -11,50 +11,61 @@ import functools as ft import logging from homeassistant.core import callback as async_callback -from homeassistant.const import EVENT_TIME_CHANGED, ATTR_FRIENDLY_NAME, \ - ATTR_ENTITY_PICTURE +from homeassistant.const import ( + EVENT_TIME_CHANGED, + ATTR_FRIENDLY_NAME, + ATTR_ENTITY_PICTURE, +) from homeassistant.loader import bind_hass from homeassistant.helpers.entity import async_generate_entity_id from homeassistant.util.async_ import run_callback_threadsafe _LOGGER = logging.getLogger(__name__) -_KEY_INSTANCE = 'configurator' +_KEY_INSTANCE = "configurator" -DATA_REQUESTS = 'configurator_requests' +DATA_REQUESTS = "configurator_requests" -ATTR_CONFIGURE_ID = 'configure_id' -ATTR_DESCRIPTION = 'description' -ATTR_DESCRIPTION_IMAGE = 'description_image' -ATTR_ERRORS = 'errors' -ATTR_FIELDS = 'fields' -ATTR_LINK_NAME = 'link_name' -ATTR_LINK_URL = 'link_url' -ATTR_SUBMIT_CAPTION = 'submit_caption' +ATTR_CONFIGURE_ID = "configure_id" +ATTR_DESCRIPTION = "description" +ATTR_DESCRIPTION_IMAGE = "description_image" +ATTR_ERRORS = "errors" +ATTR_FIELDS = "fields" +ATTR_LINK_NAME = "link_name" +ATTR_LINK_URL = "link_url" +ATTR_SUBMIT_CAPTION = "submit_caption" -DOMAIN = 'configurator' +DOMAIN = "configurator" -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" -SERVICE_CONFIGURE = 'configure' -STATE_CONFIGURE = 'configure' -STATE_CONFIGURED = 'configured' +SERVICE_CONFIGURE = "configure" +STATE_CONFIGURE = "configure" +STATE_CONFIGURED = "configured" @bind_hass @async_callback def async_request_config( - hass, name, callback=None, description=None, description_image=None, - submit_caption=None, fields=None, link_name=None, link_url=None, - entity_picture=None): + hass, + name, + callback=None, + description=None, + description_image=None, + submit_caption=None, + fields=None, + link_name=None, + link_url=None, + entity_picture=None, +): """Create a new request for configuration. Will return an ID to be used for sequent calls. """ if link_name is not None and link_url is not None: - description += '\n\n[{}]({})'.format(link_name, link_url) + description += "\n\n[{}]({})".format(link_name, link_url) if description_image is not None: - description += '\n\n![Description image]({})'.format(description_image) + description += "\n\n![Description image]({})".format(description_image) instance = hass.data.get(_KEY_INSTANCE) @@ -62,7 +73,8 @@ def async_request_config( instance = hass.data[_KEY_INSTANCE] = Configurator(hass) request_id = instance.async_request_config( - name, callback, description, submit_caption, fields, entity_picture) + name, callback, description, submit_caption, fields, entity_picture + ) if DATA_REQUESTS not in hass.data: hass.data[DATA_REQUESTS] = {} @@ -88,8 +100,7 @@ def request_config(hass, *args, **kwargs): def async_notify_errors(hass, request_id, error): """Add errors to a config request.""" try: - hass.data[DATA_REQUESTS][request_id].async_notify_errors( - request_id, error) + hass.data[DATA_REQUESTS][request_id].async_notify_errors(request_id, error) except KeyError: # If request_id does not exist pass @@ -137,15 +148,15 @@ class Configurator: self._cur_id = 0 self._requests = {} hass.services.async_register( - DOMAIN, SERVICE_CONFIGURE, self.async_handle_service_call) + DOMAIN, SERVICE_CONFIGURE, self.async_handle_service_call + ) @async_callback def async_request_config( - self, name, callback, description, submit_caption, fields, - entity_picture): + self, name, callback, description, submit_caption, fields, entity_picture + ): """Set up a request for configuration.""" - entity_id = async_generate_entity_id( - ENTITY_ID_FORMAT, name, hass=self.hass) + entity_id = async_generate_entity_id(ENTITY_ID_FORMAT, name, hass=self.hass) if fields is None: fields = [] @@ -161,12 +172,16 @@ class Configurator: ATTR_ENTITY_PICTURE: entity_picture, } - data.update({ - key: value for key, value in [ - (ATTR_DESCRIPTION, description), - (ATTR_SUBMIT_CAPTION, submit_caption), - ] if value is not None - }) + data.update( + { + key: value + for key, value in [ + (ATTR_DESCRIPTION, description), + (ATTR_SUBMIT_CAPTION, submit_caption), + ] + if value is not None + } + ) self.hass.states.async_set(entity_id, STATE_CONFIGURE, data) @@ -220,8 +235,7 @@ class Configurator: # field validation goes here? if callback: - yield from self.hass.async_add_job(callback, - call.data.get(ATTR_FIELDS, {})) + yield from self.hass.async_add_job(callback, call.data.get(ATTR_FIELDS, {})) def _generate_unique_id(self): """Generate a unique configurator ID.""" diff --git a/homeassistant/components/conversation/__init__.py b/homeassistant/components/conversation/__init__.py index d8d386f5c..ec6044f30 100644 --- a/homeassistant/components/conversation/__init__.py +++ b/homeassistant/components/conversation/__init__.py @@ -12,45 +12,48 @@ import voluptuous as vol from homeassistant import core from homeassistant.components import http from homeassistant.components.conversation.util import create_matcher -from homeassistant.components.http.data_validator import ( - RequestDataValidator) -from homeassistant.components.cover import (INTENT_OPEN_COVER, - INTENT_CLOSE_COVER) +from homeassistant.components.http.data_validator import RequestDataValidator +from homeassistant.components.cover import INTENT_OPEN_COVER, INTENT_CLOSE_COVER from homeassistant.const import EVENT_COMPONENT_LOADED from homeassistant.core import callback from homeassistant.helpers import config_validation as cv from homeassistant.helpers import intent from homeassistant.loader import bind_hass -from homeassistant.setup import (ATTR_COMPONENT) +from homeassistant.setup import ATTR_COMPONENT _LOGGER = logging.getLogger(__name__) -ATTR_TEXT = 'text' +ATTR_TEXT = "text" -DEPENDENCIES = ['http'] -DOMAIN = 'conversation' +DEPENDENCIES = ["http"] +DOMAIN = "conversation" -REGEX_TURN_COMMAND = re.compile(r'turn (?P(?: |\w)+) (?P\w+)') -REGEX_TYPE = type(re.compile('')) +REGEX_TURN_COMMAND = re.compile(r"turn (?P(?: |\w)+) (?P\w+)") +REGEX_TYPE = type(re.compile("")) UTTERANCES = { - 'cover': { - INTENT_OPEN_COVER: ['Open [the] [a] [an] {name}[s]'], - INTENT_CLOSE_COVER: ['Close [the] [a] [an] {name}[s]'] + "cover": { + INTENT_OPEN_COVER: ["Open [the] [a] [an] {name}[s]"], + INTENT_CLOSE_COVER: ["Close [the] [a] [an] {name}[s]"], } } -SERVICE_PROCESS = 'process' +SERVICE_PROCESS = "process" -SERVICE_PROCESS_SCHEMA = vol.Schema({ - vol.Required(ATTR_TEXT): cv.string, -}) +SERVICE_PROCESS_SCHEMA = vol.Schema({vol.Required(ATTR_TEXT): cv.string}) -CONFIG_SCHEMA = vol.Schema({DOMAIN: vol.Schema({ - vol.Optional('intents'): vol.Schema({ - cv.string: vol.All(cv.ensure_list, [cv.string]) - }) -})}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional("intents"): vol.Schema( + {cv.string: vol.All(cv.ensure_list, [cv.string])} + ) + } + ) + }, + extra=vol.ALLOW_EXTRA, +) @core.callback @@ -86,7 +89,7 @@ async def async_setup(hass, config): if intents is None: intents = hass.data[DOMAIN] = {} - for intent_type, utterances in config.get('intents', {}).items(): + for intent_type, utterances in config.get("intents", {}).items(): conf = intents.get(intent_type) if conf is None: @@ -97,14 +100,15 @@ async def async_setup(hass, config): async def process(service): """Parse text into commands.""" text = service.data[ATTR_TEXT] - _LOGGER.debug('Processing: <%s>', text) + _LOGGER.debug("Processing: <%s>", text) try: await _process(hass, text) except intent.IntentHandleError as err: - _LOGGER.error('Error processing %s: %s', text, err) + _LOGGER.error("Error processing %s: %s", text, err) hass.services.async_register( - DOMAIN, SERVICE_PROCESS, process, schema=SERVICE_PROCESS_SCHEMA) + DOMAIN, SERVICE_PROCESS, process, schema=SERVICE_PROCESS_SCHEMA + ) hass.http.register_view(ConversationProcessView) @@ -112,18 +116,21 @@ async def async_setup(hass, config): # if a letter is not there. By removing 's' we can match singular and # plural names. - async_register(hass, intent.INTENT_TURN_ON, [ - 'Turn [the] [a] {name}[s] on', - 'Turn on [the] [a] [an] {name}[s]', - ]) - async_register(hass, intent.INTENT_TURN_OFF, [ - 'Turn [the] [a] [an] {name}[s] off', - 'Turn off [the] [a] [an] {name}[s]', - ]) - async_register(hass, intent.INTENT_TOGGLE, [ - 'Toggle [the] [a] [an] {name}[s]', - '[the] [a] [an] {name}[s] toggle', - ]) + async_register( + hass, + intent.INTENT_TURN_ON, + ["Turn [the] [a] {name}[s] on", "Turn on [the] [a] [an] {name}[s]"], + ) + async_register( + hass, + intent.INTENT_TURN_OFF, + ["Turn [the] [a] [an] {name}[s] off", "Turn off [the] [a] [an] {name}[s]"], + ) + async_register( + hass, + intent.INTENT_TOGGLE, + ["Toggle [the] [a] [an] {name}[s]", "[the] [a] [an] {name}[s] toggle"], + ) @callback def register_utterances(component): @@ -159,27 +166,27 @@ async def _process(hass, text): continue response = await hass.helpers.intent.async_handle( - DOMAIN, intent_type, - {key: {'value': value} for key, value - in match.groupdict().items()}, text) + DOMAIN, + intent_type, + {key: {"value": value} for key, value in match.groupdict().items()}, + text, + ) return response class ConversationProcessView(http.HomeAssistantView): """View to retrieve shopping list content.""" - url = '/api/conversation/process' + url = "/api/conversation/process" name = "api:conversation:process" - @RequestDataValidator(vol.Schema({ - vol.Required('text'): str, - })) + @RequestDataValidator(vol.Schema({vol.Required("text"): str})) async def post(self, request, data): """Send a request for processing.""" - hass = request.app['hass'] + hass = request.app["hass"] try: - intent_result = await _process(hass, data['text']) + intent_result = await _process(hass, data["text"]) except intent.IntentHandleError as err: intent_result = intent.IntentResponse() intent_result.async_set_speech(str(err)) diff --git a/homeassistant/components/conversation/util.py b/homeassistant/components/conversation/util.py index 60d861afd..4904cb9f9 100644 --- a/homeassistant/components/conversation/util.py +++ b/homeassistant/components/conversation/util.py @@ -6,13 +6,13 @@ def create_matcher(utterance): """Create a regex that matches the utterance.""" # Split utterance into parts that are type: NORMAL, GROUP or OPTIONAL # Pattern matches (GROUP|OPTIONAL): Change light to [the color] {name} - parts = re.split(r'({\w+}|\[[\w\s]+\] *)', utterance) + parts = re.split(r"({\w+}|\[[\w\s]+\] *)", utterance) # Pattern to extract name from GROUP part. Matches {name} - group_matcher = re.compile(r'{(\w+)}') + group_matcher = re.compile(r"{(\w+)}") # Pattern to extract text from OPTIONAL part. Matches [the color] - optional_matcher = re.compile(r'\[([\w ]+)\] *') + optional_matcher = re.compile(r"\[([\w ]+)\] *") - pattern = ['^'] + pattern = ["^"] for part in parts: group_match = group_matcher.match(part) optional_match = optional_matcher.match(part) @@ -24,12 +24,11 @@ def create_matcher(utterance): # Group part if group_match is not None: - pattern.append( - r'(?P<{}>[\w ]+?)\s*'.format(group_match.groups()[0])) + pattern.append(r"(?P<{}>[\w ]+?)\s*".format(group_match.groups()[0])) # Optional part elif optional_match is not None: - pattern.append(r'(?:{} *)?'.format(optional_match.groups()[0])) + pattern.append(r"(?:{} *)?".format(optional_match.groups()[0])) - pattern.append('$') - return re.compile(''.join(pattern), re.I) + pattern.append("$") + return re.compile("".join(pattern), re.I) diff --git a/homeassistant/components/counter/__init__.py b/homeassistant/components/counter/__init__.py index d720819a0..52037ab93 100644 --- a/homeassistant/components/counter/__init__.py +++ b/homeassistant/components/counter/__init__.py @@ -18,37 +18,44 @@ from homeassistant.loader import bind_hass _LOGGER = logging.getLogger(__name__) -ATTR_INITIAL = 'initial' -ATTR_STEP = 'step' +ATTR_INITIAL = "initial" +ATTR_STEP = "step" -CONF_INITIAL = 'initial' -CONF_STEP = 'step' +CONF_INITIAL = "initial" +CONF_STEP = "step" DEFAULT_INITIAL = 0 DEFAULT_STEP = 1 -DOMAIN = 'counter' +DOMAIN = "counter" -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" -SERVICE_DECREMENT = 'decrement' -SERVICE_INCREMENT = 'increment' -SERVICE_RESET = 'reset' +SERVICE_DECREMENT = "decrement" +SERVICE_INCREMENT = "increment" +SERVICE_RESET = "reset" -SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, -}) +SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - cv.slug: vol.Any({ - vol.Optional(CONF_ICON): cv.icon, - vol.Optional(CONF_INITIAL, default=DEFAULT_INITIAL): - cv.positive_int, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.positive_int, - }, None) - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + cv.slug: vol.Any( + { + vol.Optional(CONF_ICON): cv.icon, + vol.Optional( + CONF_INITIAL, default=DEFAULT_INITIAL + ): cv.positive_int, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_STEP, default=DEFAULT_STEP): cv.positive_int, + }, + None, + ) + } + ) + }, + extra=vol.ALLOW_EXTRA, +) @bind_hass @@ -61,8 +68,9 @@ def increment(hass, entity_id): @bind_hass def async_increment(hass, entity_id): """Increment a counter.""" - hass.async_add_job(hass.services.async_call( - DOMAIN, SERVICE_INCREMENT, {ATTR_ENTITY_ID: entity_id})) + hass.async_add_job( + hass.services.async_call(DOMAIN, SERVICE_INCREMENT, {ATTR_ENTITY_ID: entity_id}) + ) @bind_hass @@ -75,8 +83,9 @@ def decrement(hass, entity_id): @bind_hass def async_decrement(hass, entity_id): """Decrement a counter.""" - hass.async_add_job(hass.services.async_call( - DOMAIN, SERVICE_DECREMENT, {ATTR_ENTITY_ID: entity_id})) + hass.async_add_job( + hass.services.async_call(DOMAIN, SERVICE_DECREMENT, {ATTR_ENTITY_ID: entity_id}) + ) @bind_hass @@ -89,8 +98,9 @@ def reset(hass, entity_id): @bind_hass def async_reset(hass, entity_id): """Reset a counter.""" - hass.async_add_job(hass.services.async_call( - DOMAIN, SERVICE_RESET, {ATTR_ENTITY_ID: entity_id})) + hass.async_add_job( + hass.services.async_call(DOMAIN, SERVICE_RESET, {ATTR_ENTITY_ID: entity_id}) + ) async def async_setup(hass, config): @@ -114,14 +124,14 @@ async def async_setup(hass, config): return False component.async_register_entity_service( - SERVICE_INCREMENT, SERVICE_SCHEMA, - 'async_increment') + SERVICE_INCREMENT, SERVICE_SCHEMA, "async_increment" + ) component.async_register_entity_service( - SERVICE_DECREMENT, SERVICE_SCHEMA, - 'async_decrement') + SERVICE_DECREMENT, SERVICE_SCHEMA, "async_decrement" + ) component.async_register_entity_service( - SERVICE_RESET, SERVICE_SCHEMA, - 'async_reset') + SERVICE_RESET, SERVICE_SCHEMA, "async_reset" + ) await component.async_add_entities(entities) return True @@ -161,10 +171,7 @@ class Counter(Entity): @property def state_attributes(self): """Return the state attributes.""" - return { - ATTR_INITIAL: self._initial, - ATTR_STEP: self._step, - } + return {ATTR_INITIAL: self._initial, ATTR_STEP: self._step} async def async_added_to_hass(self): """Call when entity about to be added to Home Assistant.""" diff --git a/homeassistant/components/cover/__init__.py b/homeassistant/components/cover/__init__.py index 05c5e46e4..4398c13a0 100644 --- a/homeassistant/components/cover/__init__.py +++ b/homeassistant/components/cover/__init__.py @@ -18,26 +18,34 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components import group from homeassistant.helpers import intent from homeassistant.const import ( - SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION, - SERVICE_STOP_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_CLOSE_COVER_TILT, - SERVICE_STOP_COVER_TILT, SERVICE_SET_COVER_TILT_POSITION, STATE_OPEN, - STATE_CLOSED, STATE_UNKNOWN, STATE_OPENING, STATE_CLOSING, ATTR_ENTITY_ID) + SERVICE_OPEN_COVER, + SERVICE_CLOSE_COVER, + SERVICE_SET_COVER_POSITION, + SERVICE_STOP_COVER, + SERVICE_OPEN_COVER_TILT, + SERVICE_CLOSE_COVER_TILT, + SERVICE_STOP_COVER_TILT, + SERVICE_SET_COVER_TILT_POSITION, + STATE_OPEN, + STATE_CLOSED, + STATE_UNKNOWN, + STATE_OPENING, + STATE_CLOSING, + ATTR_ENTITY_ID, +) _LOGGER = logging.getLogger(__name__) -DOMAIN = 'cover' -DEPENDENCIES = ['group'] +DOMAIN = "cover" +DEPENDENCIES = ["group"] SCAN_INTERVAL = timedelta(seconds=15) -GROUP_NAME_ALL_COVERS = 'all covers' -ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format('all_covers') +GROUP_NAME_ALL_COVERS = "all covers" +ENTITY_ID_ALL_COVERS = group.ENTITY_ID_FORMAT.format("all_covers") -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" -DEVICE_CLASSES = [ - 'window', # Window control - 'garage', # Garage door control -] +DEVICE_CLASSES = ["window", "garage"] # Window control # Garage door control DEVICE_CLASSES_SCHEMA = vol.All(vol.Lower, vol.In(DEVICE_CLASSES)) @@ -50,27 +58,27 @@ SUPPORT_CLOSE_TILT = 32 SUPPORT_STOP_TILT = 64 SUPPORT_SET_TILT_POSITION = 128 -ATTR_CURRENT_POSITION = 'current_position' -ATTR_CURRENT_TILT_POSITION = 'current_tilt_position' -ATTR_POSITION = 'position' -ATTR_TILT_POSITION = 'tilt_position' +ATTR_CURRENT_POSITION = "current_position" +ATTR_CURRENT_TILT_POSITION = "current_tilt_position" +ATTR_POSITION = "position" +ATTR_TILT_POSITION = "tilt_position" -INTENT_OPEN_COVER = 'HassOpenCover' -INTENT_CLOSE_COVER = 'HassCloseCover' +INTENT_OPEN_COVER = "HassOpenCover" +INTENT_CLOSE_COVER = "HassCloseCover" -COVER_SERVICE_SCHEMA = vol.Schema({ - vol.Optional(ATTR_ENTITY_ID): cv.entity_ids, -}) +COVER_SERVICE_SCHEMA = vol.Schema({vol.Optional(ATTR_ENTITY_ID): cv.entity_ids}) -COVER_SET_COVER_POSITION_SCHEMA = COVER_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_POSITION): - vol.All(vol.Coerce(int), vol.Range(min=0, max=100)), -}) +COVER_SET_COVER_POSITION_SCHEMA = COVER_SERVICE_SCHEMA.extend( + {vol.Required(ATTR_POSITION): vol.All(vol.Coerce(int), vol.Range(min=0, max=100))} +) -COVER_SET_COVER_TILT_POSITION_SCHEMA = COVER_SERVICE_SCHEMA.extend({ - vol.Required(ATTR_TILT_POSITION): - vol.All(vol.Coerce(int), vol.Range(min=0, max=100)), -}) +COVER_SET_COVER_TILT_POSITION_SCHEMA = COVER_SERVICE_SCHEMA.extend( + { + vol.Required(ATTR_TILT_POSITION): vol.All( + vol.Coerce(int), vol.Range(min=0, max=100) + ) + } +) @bind_hass @@ -141,56 +149,57 @@ def stop_cover_tilt(hass, entity_id=None): async def async_setup(hass, config): """Track states and offer events for covers.""" component = EntityComponent( - _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_COVERS) + _LOGGER, DOMAIN, hass, SCAN_INTERVAL, GROUP_NAME_ALL_COVERS + ) await component.async_setup(config) component.async_register_entity_service( - SERVICE_OPEN_COVER, COVER_SERVICE_SCHEMA, - 'async_open_cover' + SERVICE_OPEN_COVER, COVER_SERVICE_SCHEMA, "async_open_cover" ) component.async_register_entity_service( - SERVICE_CLOSE_COVER, COVER_SERVICE_SCHEMA, - 'async_close_cover' + SERVICE_CLOSE_COVER, COVER_SERVICE_SCHEMA, "async_close_cover" ) component.async_register_entity_service( - SERVICE_SET_COVER_POSITION, COVER_SET_COVER_POSITION_SCHEMA, - 'async_set_cover_position' + SERVICE_SET_COVER_POSITION, + COVER_SET_COVER_POSITION_SCHEMA, + "async_set_cover_position", ) component.async_register_entity_service( - SERVICE_STOP_COVER, COVER_SERVICE_SCHEMA, - 'async_stop_cover' + SERVICE_STOP_COVER, COVER_SERVICE_SCHEMA, "async_stop_cover" ) component.async_register_entity_service( - SERVICE_OPEN_COVER_TILT, COVER_SERVICE_SCHEMA, - 'async_open_cover_tilt' + SERVICE_OPEN_COVER_TILT, COVER_SERVICE_SCHEMA, "async_open_cover_tilt" ) component.async_register_entity_service( - SERVICE_CLOSE_COVER_TILT, COVER_SERVICE_SCHEMA, - 'async_close_cover_tilt' + SERVICE_CLOSE_COVER_TILT, COVER_SERVICE_SCHEMA, "async_close_cover_tilt" ) component.async_register_entity_service( - SERVICE_STOP_COVER_TILT, COVER_SERVICE_SCHEMA, - 'async_stop_cover_tilt' + SERVICE_STOP_COVER_TILT, COVER_SERVICE_SCHEMA, "async_stop_cover_tilt" ) component.async_register_entity_service( - SERVICE_SET_COVER_TILT_POSITION, COVER_SET_COVER_TILT_POSITION_SCHEMA, - 'async_set_cover_tilt_position' + SERVICE_SET_COVER_TILT_POSITION, + COVER_SET_COVER_TILT_POSITION_SCHEMA, + "async_set_cover_tilt_position", ) - hass.helpers.intent.async_register(intent.ServiceIntentHandler( - INTENT_OPEN_COVER, DOMAIN, SERVICE_OPEN_COVER, - "Opened {}")) - hass.helpers.intent.async_register(intent.ServiceIntentHandler( - INTENT_CLOSE_COVER, DOMAIN, SERVICE_CLOSE_COVER, - "Closed {}")) + hass.helpers.intent.async_register( + intent.ServiceIntentHandler( + INTENT_OPEN_COVER, DOMAIN, SERVICE_OPEN_COVER, "Opened {}" + ) + ) + hass.helpers.intent.async_register( + intent.ServiceIntentHandler( + INTENT_CLOSE_COVER, DOMAIN, SERVICE_CLOSE_COVER, "Closed {}" + ) + ) return True @@ -254,8 +263,11 @@ class CoverDevice(Entity): if self.current_cover_tilt_position is not None: supported_features |= ( - SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT | - SUPPORT_SET_TILT_POSITION) + SUPPORT_OPEN_TILT + | SUPPORT_CLOSE_TILT + | SUPPORT_STOP_TILT + | SUPPORT_SET_TILT_POSITION + ) return supported_features @@ -305,8 +317,7 @@ class CoverDevice(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job( - ft.partial(self.set_cover_position, **kwargs)) + return self.hass.async_add_job(ft.partial(self.set_cover_position, **kwargs)) def stop_cover(self, **kwargs): """Stop the cover.""" @@ -328,8 +339,7 @@ class CoverDevice(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job( - ft.partial(self.open_cover_tilt, **kwargs)) + return self.hass.async_add_job(ft.partial(self.open_cover_tilt, **kwargs)) def close_cover_tilt(self, **kwargs): """Close the cover tilt.""" @@ -340,8 +350,7 @@ class CoverDevice(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job( - ft.partial(self.close_cover_tilt, **kwargs)) + return self.hass.async_add_job(ft.partial(self.close_cover_tilt, **kwargs)) def set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" @@ -353,7 +362,8 @@ class CoverDevice(Entity): This method must be run in the event loop and returns a coroutine. """ return self.hass.async_add_job( - ft.partial(self.set_cover_tilt_position, **kwargs)) + ft.partial(self.set_cover_tilt_position, **kwargs) + ) def stop_cover_tilt(self, **kwargs): """Stop the cover.""" @@ -364,5 +374,4 @@ class CoverDevice(Entity): This method must be run in the event loop and returns a coroutine. """ - return self.hass.async_add_job( - ft.partial(self.stop_cover_tilt, **kwargs)) + return self.hass.async_add_job(ft.partial(self.stop_cover_tilt, **kwargs)) diff --git a/homeassistant/components/cover/abode.py b/homeassistant/components/cover/abode.py index 3ba3fb118..a02bb591d 100644 --- a/homeassistant/components/cover/abode.py +++ b/homeassistant/components/cover/abode.py @@ -10,7 +10,7 @@ from homeassistant.components.abode import AbodeDevice, DOMAIN as ABODE_DOMAIN from homeassistant.components.cover import CoverDevice -DEPENDENCIES = ['abode'] +DEPENDENCIES = ["abode"] _LOGGER = logging.getLogger(__name__) diff --git a/homeassistant/components/cover/aladdin_connect.py b/homeassistant/components/cover/aladdin_connect.py index 4627ba777..2a912d9a7 100644 --- a/homeassistant/components/cover/aladdin_connect.py +++ b/homeassistant/components/cover/aladdin_connect.py @@ -8,32 +8,41 @@ import logging import voluptuous as vol -from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA, - SUPPORT_OPEN, SUPPORT_CLOSE) -from homeassistant.const import (CONF_USERNAME, CONF_PASSWORD, STATE_CLOSED, - STATE_OPENING, STATE_CLOSING, STATE_OPEN) +from homeassistant.components.cover import ( + CoverDevice, + PLATFORM_SCHEMA, + SUPPORT_OPEN, + SUPPORT_CLOSE, +) +from homeassistant.const import ( + CONF_USERNAME, + CONF_PASSWORD, + STATE_CLOSED, + STATE_OPENING, + STATE_CLOSING, + STATE_OPEN, +) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['aladdin_connect==0.3'] +REQUIREMENTS = ["aladdin_connect==0.3"] _LOGGER = logging.getLogger(__name__) -NOTIFICATION_ID = 'aladdin_notification' -NOTIFICATION_TITLE = 'Aladdin Connect Cover Setup' +NOTIFICATION_ID = "aladdin_notification" +NOTIFICATION_TITLE = "Aladdin Connect Cover Setup" STATES_MAP = { - 'open': STATE_OPEN, - 'opening': STATE_OPENING, - 'closed': STATE_CLOSED, - 'closing': STATE_CLOSING + "open": STATE_OPEN, + "opening": STATE_OPENING, + "closed": STATE_CLOSED, + "closing": STATE_CLOSING, } SUPPORTED_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -51,11 +60,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): except (TypeError, KeyError, NameError, ValueError) as ex: _LOGGER.error("%s", ex) hass.components.persistent_notification.create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) class AladdinDevice(CoverDevice): @@ -64,15 +74,15 @@ class AladdinDevice(CoverDevice): def __init__(self, acc, device): """Initialize the cover.""" self._acc = acc - self._device_id = device['device_id'] - self._number = device['door_number'] - self._name = device['name'] - self._status = STATES_MAP.get(device['status']) + self._device_id = device["device_id"] + self._number = device["door_number"] + self._name = device["name"] + self._status = STATES_MAP.get(device["status"]) @property def device_class(self): """Define this cover as a garage door.""" - return 'garage' + return "garage" @property def supported_features(self): @@ -82,7 +92,7 @@ class AladdinDevice(CoverDevice): @property def unique_id(self): """Return a unique ID.""" - return '{}-{}'.format(self._device_id, self._number) + return "{}-{}".format(self._device_id, self._number) @property def name(self): diff --git a/homeassistant/components/cover/brunt.py b/homeassistant/components/cover/brunt.py index 746f3840a..5a733b584 100644 --- a/homeassistant/components/cover/brunt.py +++ b/homeassistant/components/cover/brunt.py @@ -9,60 +9,67 @@ import logging import voluptuous as vol -from homeassistant.const import ( - ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME) +from homeassistant.const import ATTR_ATTRIBUTION, CONF_PASSWORD, CONF_USERNAME from homeassistant.components.cover import ( - ATTR_POSITION, CoverDevice, - PLATFORM_SCHEMA, SUPPORT_CLOSE, - SUPPORT_OPEN, SUPPORT_SET_POSITION + ATTR_POSITION, + CoverDevice, + PLATFORM_SCHEMA, + SUPPORT_CLOSE, + SUPPORT_OPEN, + SUPPORT_SET_POSITION, ) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['brunt==0.1.3'] +REQUIREMENTS = ["brunt==0.1.3"] _LOGGER = logging.getLogger(__name__) COVER_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION -DEVICE_CLASS = 'window' +DEVICE_CLASS = "window" -ATTR_REQUEST_POSITION = 'request_position' -NOTIFICATION_ID = 'brunt_notification' -NOTIFICATION_TITLE = 'Brunt Cover Setup' -ATTRIBUTION = 'Based on an unofficial Brunt SDK.' +ATTR_REQUEST_POSITION = "request_position" +NOTIFICATION_ID = "brunt_notification" +NOTIFICATION_TITLE = "Brunt Cover Setup" +ATTRIBUTION = "Based on an unofficial Brunt SDK." CLOSED_POSITION = 0 OPEN_POSITION = 100 -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_USERNAME): cv.string, vol.Required(CONF_PASSWORD): cv.string} +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the brunt platform.""" # pylint: disable=no-name-in-module from brunt import BruntAPI + username = config[CONF_USERNAME] password = config[CONF_PASSWORD] bapi = BruntAPI(username=username, password=password) try: - things = bapi.getThings()['things'] + things = bapi.getThings()["things"] if not things: _LOGGER.error("No things present in account.") else: - add_entities([BruntDevice( - bapi, thing['NAME'], - thing['thingUri']) for thing in things], True) + add_entities( + [ + BruntDevice(bapi, thing["NAME"], thing["thingUri"]) + for thing in things + ], + True, + ) except (TypeError, KeyError, NameError, ValueError) as ex: _LOGGER.error("%s", ex) hass.components.persistent_notification.create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) class BruntDevice(CoverDevice): @@ -98,7 +105,7 @@ class BruntDevice(CoverDevice): None is unknown, 0 is closed, 100 is fully open. """ - pos = self._state.get('currentPosition') + pos = self._state.get("currentPosition") return int(pos) if pos else None @property @@ -110,7 +117,7 @@ class BruntDevice(CoverDevice): to Brunt, at times there is a diff of 1 to current None is unknown, 0 is closed, 100 is fully open. """ - pos = self._state.get('requestPosition') + pos = self._state.get("requestPosition") return int(pos) if pos else None @property @@ -120,7 +127,7 @@ class BruntDevice(CoverDevice): None is unknown, 0 when stopped, 1 when opening, 2 when closing """ - mov = self._state.get('moveState') + mov = self._state.get("moveState") return int(mov) if mov else None @property @@ -138,7 +145,7 @@ class BruntDevice(CoverDevice): """Return the detailed device state attributes.""" return { ATTR_ATTRIBUTION: ATTRIBUTION, - ATTR_REQUEST_POSITION: self.request_cover_position + ATTR_REQUEST_POSITION: self.request_cover_position, } @property @@ -159,8 +166,7 @@ class BruntDevice(CoverDevice): def update(self): """Poll the current state of the device.""" try: - self._state = self._bapi.getState( - thingUri=self._thing_uri).get('thing') + self._state = self._bapi.getState(thingUri=self._thing_uri).get("thing") self._available = True except (TypeError, KeyError, NameError, ValueError) as ex: _LOGGER.error("%s", ex) @@ -168,15 +174,14 @@ class BruntDevice(CoverDevice): def open_cover(self, **kwargs): """Set the cover to the open position.""" - self._bapi.changeRequestPosition( - OPEN_POSITION, thingUri=self._thing_uri) + self._bapi.changeRequestPosition(OPEN_POSITION, thingUri=self._thing_uri) def close_cover(self, **kwargs): """Set the cover to the closed position.""" - self._bapi.changeRequestPosition( - CLOSED_POSITION, thingUri=self._thing_uri) + self._bapi.changeRequestPosition(CLOSED_POSITION, thingUri=self._thing_uri) def set_cover_position(self, **kwargs): """Set the cover to a specific position.""" self._bapi.changeRequestPosition( - kwargs[ATTR_POSITION], thingUri=self._thing_uri) + kwargs[ATTR_POSITION], thingUri=self._thing_uri + ) diff --git a/homeassistant/components/cover/command_line.py b/homeassistant/components/cover/command_line.py index bebf78b1d..3f58f6561 100644 --- a/homeassistant/components/cover/command_line.py +++ b/homeassistant/components/cover/command_line.py @@ -9,26 +9,34 @@ import subprocess import voluptuous as vol -from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA) +from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA from homeassistant.const import ( - CONF_COMMAND_CLOSE, CONF_COMMAND_OPEN, CONF_COMMAND_STATE, - CONF_COMMAND_STOP, CONF_COVERS, CONF_VALUE_TEMPLATE, CONF_FRIENDLY_NAME) + CONF_COMMAND_CLOSE, + CONF_COMMAND_OPEN, + CONF_COMMAND_STATE, + CONF_COMMAND_STOP, + CONF_COVERS, + CONF_VALUE_TEMPLATE, + CONF_FRIENDLY_NAME, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -COVER_SCHEMA = vol.Schema({ - vol.Optional(CONF_COMMAND_CLOSE, default='true'): cv.string, - vol.Optional(CONF_COMMAND_OPEN, default='true'): cv.string, - vol.Optional(CONF_COMMAND_STATE): cv.string, - vol.Optional(CONF_COMMAND_STOP, default='true'): cv.string, - vol.Optional(CONF_FRIENDLY_NAME): cv.string, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, -}) +COVER_SCHEMA = vol.Schema( + { + vol.Optional(CONF_COMMAND_CLOSE, default="true"): cv.string, + vol.Optional(CONF_COMMAND_OPEN, default="true"): cv.string, + vol.Optional(CONF_COMMAND_STATE): cv.string, + vol.Optional(CONF_COMMAND_STOP, default="true"): cv.string, + vol.Optional(CONF_FRIENDLY_NAME): cv.string, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA})} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -63,8 +71,16 @@ def setup_platform(hass, config, add_entities, discovery_info=None): class CommandCover(CoverDevice): """Representation a command line cover.""" - def __init__(self, hass, name, command_open, command_close, command_stop, - command_state, value_template): + def __init__( + self, + hass, + name, + command_open, + command_close, + command_stop, + command_state, + value_template, + ): """Initialize the cover.""" self._hass = hass self._name = name @@ -80,7 +96,7 @@ class CommandCover(CoverDevice): """Execute the actual commands.""" _LOGGER.info("Running command: %s", command) - success = (subprocess.call(command, shell=True) == 0) + success = subprocess.call(command, shell=True) == 0 if not success: _LOGGER.error("Command failed: %s", command) @@ -94,7 +110,7 @@ class CommandCover(CoverDevice): try: return_value = subprocess.check_output(command, shell=True) - return return_value.strip().decode('utf-8') + return return_value.strip().decode("utf-8") except subprocess.CalledProcessError: _LOGGER.error("Command failed: %s", command) @@ -134,8 +150,7 @@ class CommandCover(CoverDevice): if self._command_state: payload = str(self._query_state()) if self._value_template: - payload = self._value_template.render_with_possible_json_value( - payload) + payload = self._value_template.render_with_possible_json_value(payload) self._state = int(payload) def open_cover(self, **kwargs): diff --git a/homeassistant/components/cover/demo.py b/homeassistant/components/cover/demo.py index 21add0a6c..9540c1430 100644 --- a/homeassistant/components/cover/demo.py +++ b/homeassistant/components/cover/demo.py @@ -5,27 +5,44 @@ For more details about this platform, please refer to the documentation https://home-assistant.io/components/demo/ """ from homeassistant.components.cover import ( - CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION, - ATTR_TILT_POSITION) + CoverDevice, + SUPPORT_OPEN, + SUPPORT_CLOSE, + ATTR_POSITION, + ATTR_TILT_POSITION, +) from homeassistant.helpers.event import track_utc_time_change def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Demo covers.""" - add_entities([ - DemoCover(hass, 'Kitchen Window'), - DemoCover(hass, 'Hall Window', 10), - DemoCover(hass, 'Living Room Window', 70, 50), - DemoCover(hass, 'Garage Door', device_class='garage', - supported_features=(SUPPORT_OPEN | SUPPORT_CLOSE)), - ]) + add_entities( + [ + DemoCover(hass, "Kitchen Window"), + DemoCover(hass, "Hall Window", 10), + DemoCover(hass, "Living Room Window", 70, 50), + DemoCover( + hass, + "Garage Door", + device_class="garage", + supported_features=(SUPPORT_OPEN | SUPPORT_CLOSE), + ), + ] + ) class DemoCover(CoverDevice): """Representation of a demo cover.""" - def __init__(self, hass, name, position=None, tilt_position=None, - device_class=None, supported_features=None): + def __init__( + self, + hass, + name, + position=None, + tilt_position=None, + device_class=None, + supported_features=None, + ): """Initialize the cover.""" self.hass = hass self._name = name @@ -182,7 +199,8 @@ class DemoCover(CoverDevice): """Listen for changes in cover.""" if self._unsub_listener_cover is None: self._unsub_listener_cover = track_utc_time_change( - self.hass, self._time_changed_cover) + self.hass, self._time_changed_cover + ) def _time_changed_cover(self, now): """Track time changes.""" @@ -202,7 +220,8 @@ class DemoCover(CoverDevice): """Listen for changes in cover tilt.""" if self._unsub_listener_cover_tilt is None: self._unsub_listener_cover_tilt = track_utc_time_change( - self.hass, self._time_changed_cover_tilt) + self.hass, self._time_changed_cover_tilt + ) def _time_changed_cover_tilt(self, now): """Track time changes.""" diff --git a/homeassistant/components/cover/garadget.py b/homeassistant/components/cover/garadget.py index 7a04aa4c7..d7b577626 100644 --- a/homeassistant/components/cover/garadget.py +++ b/homeassistant/components/cover/garadget.py @@ -13,42 +13,52 @@ import homeassistant.helpers.config_validation as cv from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA from homeassistant.helpers.event import track_utc_time_change from homeassistant.const import ( - CONF_DEVICE, CONF_USERNAME, CONF_PASSWORD, CONF_ACCESS_TOKEN, CONF_NAME, - STATE_UNKNOWN, STATE_CLOSED, STATE_OPEN, CONF_COVERS) + CONF_DEVICE, + CONF_USERNAME, + CONF_PASSWORD, + CONF_ACCESS_TOKEN, + CONF_NAME, + STATE_UNKNOWN, + STATE_CLOSED, + STATE_OPEN, + CONF_COVERS, +) _LOGGER = logging.getLogger(__name__) -ATTR_AVAILABLE = 'available' -ATTR_SENSOR_STRENGTH = 'sensor_reflection_rate' -ATTR_SIGNAL_STRENGTH = 'wifi_signal_strength' -ATTR_TIME_IN_STATE = 'time_in_state' +ATTR_AVAILABLE = "available" +ATTR_SENSOR_STRENGTH = "sensor_reflection_rate" +ATTR_SIGNAL_STRENGTH = "wifi_signal_strength" +ATTR_TIME_IN_STATE = "time_in_state" -DEFAULT_NAME = 'Garadget' +DEFAULT_NAME = "Garadget" -STATE_CLOSING = 'closing' -STATE_OFFLINE = 'offline' -STATE_OPENING = 'opening' -STATE_STOPPED = 'stopped' +STATE_CLOSING = "closing" +STATE_OFFLINE = "offline" +STATE_OPENING = "opening" +STATE_STOPPED = "stopped" STATES_MAP = { - 'open': STATE_OPEN, - 'opening': STATE_OPENING, - 'closed': STATE_CLOSED, - 'closing': STATE_CLOSING, - 'stopped': STATE_STOPPED + "open": STATE_OPEN, + "opening": STATE_OPENING, + "closed": STATE_CLOSED, + "closing": STATE_CLOSING, + "stopped": STATE_STOPPED, } -COVER_SCHEMA = vol.Schema({ - vol.Optional(CONF_ACCESS_TOKEN): cv.string, - vol.Optional(CONF_DEVICE): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PASSWORD): cv.string, - vol.Optional(CONF_USERNAME): cv.string, -}) +COVER_SCHEMA = vol.Schema( + { + vol.Optional(CONF_ACCESS_TOKEN): cv.string, + vol.Optional(CONF_DEVICE): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PASSWORD): cv.string, + vol.Optional(CONF_USERNAME): cv.string, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA})} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -58,11 +68,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for device_id, device_config in devices.items(): args = { - 'name': device_config.get(CONF_NAME), - 'device_id': device_config.get(CONF_DEVICE, device_id), - 'username': device_config.get(CONF_USERNAME), - 'password': device_config.get(CONF_PASSWORD), - 'access_token': device_config.get(CONF_ACCESS_TOKEN) + "name": device_config.get(CONF_NAME), + "device_id": device_config.get(CONF_DEVICE, device_id), + "username": device_config.get(CONF_USERNAME), + "password": device_config.get(CONF_PASSWORD), + "access_token": device_config.get(CONF_ACCESS_TOKEN), } covers.append(GaradgetCover(hass, args)) @@ -75,14 +85,14 @@ class GaradgetCover(CoverDevice): def __init__(self, hass, args): """Initialize the cover.""" - self.particle_url = 'https://api.particle.io' + self.particle_url = "https://api.particle.io" self.hass = hass - self._name = args['name'] - self.device_id = args['device_id'] - self.access_token = args['access_token'] + self._name = args["name"] + self.device_id = args["device_id"] + self.access_token = args["access_token"] self.obtained_token = False - self._username = args['username'] - self._password = args['password'] + self._username = args["username"] + self._password = args["password"] self._state = STATE_UNKNOWN self.time_in_state = None self.signal = None @@ -96,19 +106,20 @@ class GaradgetCover(CoverDevice): try: if self._name is None: - doorconfig = self._get_variable('doorConfig') - if doorconfig['nme'] is not None: - self._name = doorconfig['nme'] + doorconfig = self._get_variable("doorConfig") + if doorconfig["nme"] is not None: + self._name = doorconfig["nme"] self.update() except requests.exceptions.ConnectionError as ex: - _LOGGER.error( - "Unable to connect to server: %(reason)s", dict(reason=ex)) + _LOGGER.error("Unable to connect to server: %(reason)s", dict(reason=ex)) self._state = STATE_OFFLINE self._available = False self._name = DEFAULT_NAME except KeyError as ex: - _LOGGER.warning("Garadget device %(device)s seems to be offline", - dict(device=self.device_id)) + _LOGGER.warning( + "Garadget device %(device)s seems to be offline", + dict(device=self.device_id), + ) self._name = DEFAULT_NAME self._state = STATE_OFFLINE self._available = False @@ -163,30 +174,27 @@ class GaradgetCover(CoverDevice): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return 'garage' + return "garage" def get_token(self): """Get new token for usage during this session.""" args = { - 'grant_type': 'password', - 'username': self._username, - 'password': self._password + "grant_type": "password", + "username": self._username, + "password": self._password, } - url = '{}/oauth/token'.format(self.particle_url) - ret = requests.post( - url, auth=('particle', 'particle'), data=args, timeout=10) + url = "{}/oauth/token".format(self.particle_url) + ret = requests.post(url, auth=("particle", "particle"), data=args, timeout=10) try: - return ret.json()['access_token'] + return ret.json()["access_token"] except KeyError: _LOGGER.error("Unable to retrieve access token") def remove_token(self): """Remove authorization token from API.""" - url = '{}/v1/access_tokens/{}'.format( - self.particle_url, self.access_token) - ret = requests.delete( - url, auth=(self._username, self._password), timeout=10) + url = "{}/v1/access_tokens/{}".format(self.particle_url, self.access_token) + ret = requests.delete(url, auth=(self._username, self._password), timeout=10) return ret.text def _start_watcher(self, command): @@ -194,7 +202,8 @@ class GaradgetCover(CoverDevice): _LOGGER.debug("Starting Watcher for command: %s ", command) if self._unsub_listener_cover is None: self._unsub_listener_cover = track_utc_time_change( - self.hass, self._check_state) + self.hass, self._check_state + ) def _check_state(self, now): """Check the state of the service during an operation.""" @@ -202,42 +211,43 @@ class GaradgetCover(CoverDevice): def close_cover(self, **kwargs): """Close the cover.""" - if self._state not in ['close', 'closing']: - ret = self._put_command('setState', 'close') - self._start_watcher('close') - return ret.get('return_value') == 1 + if self._state not in ["close", "closing"]: + ret = self._put_command("setState", "close") + self._start_watcher("close") + return ret.get("return_value") == 1 def open_cover(self, **kwargs): """Open the cover.""" - if self._state not in ['open', 'opening']: - ret = self._put_command('setState', 'open') - self._start_watcher('open') - return ret.get('return_value') == 1 + if self._state not in ["open", "opening"]: + ret = self._put_command("setState", "open") + self._start_watcher("open") + return ret.get("return_value") == 1 def stop_cover(self, **kwargs): """Stop the door where it is.""" - if self._state not in ['stopped']: - ret = self._put_command('setState', 'stop') - self._start_watcher('stop') - return ret['return_value'] == 1 + if self._state not in ["stopped"]: + ret = self._put_command("setState", "stop") + self._start_watcher("stop") + return ret["return_value"] == 1 def update(self): """Get updated status from API.""" try: - status = self._get_variable('doorStatus') - _LOGGER.debug("Current Status: %s", status['status']) - self._state = STATES_MAP.get(status['status'], STATE_UNKNOWN) - self.time_in_state = status['time'] - self.signal = status['signal'] - self.sensor = status['sensor'] + status = self._get_variable("doorStatus") + _LOGGER.debug("Current Status: %s", status["status"]) + self._state = STATES_MAP.get(status["status"], STATE_UNKNOWN) + self.time_in_state = status["time"] + self.signal = status["signal"] + self.sensor = status["sensor"] self._available = True except requests.exceptions.ConnectionError as ex: - _LOGGER.error( - "Unable to connect to server: %(reason)s", dict(reason=ex)) + _LOGGER.error("Unable to connect to server: %(reason)s", dict(reason=ex)) self._state = STATE_OFFLINE except KeyError as ex: - _LOGGER.warning("Garadget device %(device)s seems to be offline", - dict(device=self.device_id)) + _LOGGER.warning( + "Garadget device %(device)s seems to be offline", + dict(device=self.device_id), + ) self._state = STATE_OFFLINE if self._state not in [STATE_CLOSING, STATE_OPENING]: @@ -247,21 +257,21 @@ class GaradgetCover(CoverDevice): def _get_variable(self, var): """Get latest status.""" - url = '{}/v1/devices/{}/{}?access_token={}'.format( - self.particle_url, self.device_id, var, self.access_token) + url = "{}/v1/devices/{}/{}?access_token={}".format( + self.particle_url, self.device_id, var, self.access_token + ) ret = requests.get(url, timeout=10) result = {} - for pairs in ret.json()['result'].split('|'): - key = pairs.split('=') + for pairs in ret.json()["result"].split("|"): + key = pairs.split("=") result[key[0]] = key[1] return result def _put_command(self, func, arg=None): """Send commands to API.""" - params = {'access_token': self.access_token} + params = {"access_token": self.access_token} if arg: - params['command'] = arg - url = '{}/v1/devices/{}/{}'.format( - self.particle_url, self.device_id, func) + params["command"] = arg + url = "{}/v1/devices/{}/{}".format(self.particle_url, self.device_id, func) ret = requests.post(url, data=params, timeout=10) return ret.json() diff --git a/homeassistant/components/cover/gogogate2.py b/homeassistant/components/cover/gogogate2.py index accc4f9ec..0c9455420 100644 --- a/homeassistant/components/cover/gogogate2.py +++ b/homeassistant/components/cover/gogogate2.py @@ -8,28 +8,33 @@ import logging import voluptuous as vol -from homeassistant.components.cover import ( - CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE) +from homeassistant.components.cover import CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, STATE_CLOSED, - CONF_IP_ADDRESS, CONF_NAME) + CONF_USERNAME, + CONF_PASSWORD, + STATE_CLOSED, + CONF_IP_ADDRESS, + CONF_NAME, +) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pygogogate2==0.1.1'] +REQUIREMENTS = ["pygogogate2==0.1.1"] _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'gogogate2' +DEFAULT_NAME = "gogogate2" -NOTIFICATION_ID = 'gogogate2_notification' -NOTIFICATION_TITLE = 'Gogogate2 Cover Setup' +NOTIFICATION_ID = "gogogate2_notification" +NOTIFICATION_TITLE = "Gogogate2 Cover Setup" -COVER_SCHEMA = vol.Schema({ - vol.Required(CONF_IP_ADDRESS): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, -}) +COVER_SCHEMA = vol.Schema( + { + vol.Required(CONF_IP_ADDRESS): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -46,20 +51,19 @@ def setup_platform(hass, config, add_entities, discovery_info=None): try: devices = mygogogate2.get_devices() if devices is False: - raise ValueError( - "Username or Password is incorrect or no devices found") + raise ValueError("Username or Password is incorrect or no devices found") - add_entities(MyGogogate2Device( - mygogogate2, door, name) for door in devices) + add_entities(MyGogogate2Device(mygogogate2, door, name) for door in devices) except (TypeError, KeyError, NameError, ValueError) as ex: _LOGGER.error("%s", ex) hass.components.persistent_notification.create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) class MyGogogate2Device(CoverDevice): @@ -68,9 +72,9 @@ class MyGogogate2Device(CoverDevice): def __init__(self, mygogogate2, device, name): """Initialize with API object, device id.""" self.mygogogate2 = mygogogate2 - self.device_id = device['door'] - self._name = name or device['name'] - self._status = device['status'] + self.device_id = device["door"] + self._name = name or device["name"] + self._status = device["status"] self._available = None @property @@ -86,7 +90,7 @@ class MyGogogate2Device(CoverDevice): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return 'garage' + return "garage" @property def supported_features(self): diff --git a/homeassistant/components/cover/group.py b/homeassistant/components/cover/group.py index 0424c9007..b00ef06ab 100644 --- a/homeassistant/components/cover/group.py +++ b/homeassistant/components/cover/group.py @@ -10,40 +10,61 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.components.cover import ( - DOMAIN, PLATFORM_SCHEMA, CoverDevice, ATTR_POSITION, - ATTR_CURRENT_POSITION, ATTR_TILT_POSITION, ATTR_CURRENT_TILT_POSITION, - SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP, SUPPORT_SET_POSITION, - SUPPORT_OPEN_TILT, SUPPORT_CLOSE_TILT, - SUPPORT_STOP_TILT, SUPPORT_SET_TILT_POSITION, - SERVICE_OPEN_COVER, SERVICE_CLOSE_COVER, SERVICE_SET_COVER_POSITION, - SERVICE_STOP_COVER, SERVICE_OPEN_COVER_TILT, SERVICE_CLOSE_COVER_TILT, - SERVICE_STOP_COVER_TILT, SERVICE_SET_COVER_TILT_POSITION) + DOMAIN, + PLATFORM_SCHEMA, + CoverDevice, + ATTR_POSITION, + ATTR_CURRENT_POSITION, + ATTR_TILT_POSITION, + ATTR_CURRENT_TILT_POSITION, + SUPPORT_OPEN, + SUPPORT_CLOSE, + SUPPORT_STOP, + SUPPORT_SET_POSITION, + SUPPORT_OPEN_TILT, + SUPPORT_CLOSE_TILT, + SUPPORT_STOP_TILT, + SUPPORT_SET_TILT_POSITION, + SERVICE_OPEN_COVER, + SERVICE_CLOSE_COVER, + SERVICE_SET_COVER_POSITION, + SERVICE_STOP_COVER, + SERVICE_OPEN_COVER_TILT, + SERVICE_CLOSE_COVER_TILT, + SERVICE_STOP_COVER_TILT, + SERVICE_SET_COVER_TILT_POSITION, +) from homeassistant.const import ( - ATTR_ASSUMED_STATE, ATTR_ENTITY_ID, ATTR_SUPPORTED_FEATURES, - CONF_ENTITIES, CONF_NAME, STATE_CLOSED) + ATTR_ASSUMED_STATE, + ATTR_ENTITY_ID, + ATTR_SUPPORTED_FEATURES, + CONF_ENTITIES, + CONF_NAME, + STATE_CLOSED, +) import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_state_change _LOGGER = logging.getLogger(__name__) -KEY_OPEN_CLOSE = 'open_close' -KEY_STOP = 'stop' -KEY_POSITION = 'position' +KEY_OPEN_CLOSE = "open_close" +KEY_STOP = "stop" +KEY_POSITION = "position" -DEFAULT_NAME = 'Cover Group' +DEFAULT_NAME = "Cover Group" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Required(CONF_ENTITIES): cv.entities_domain(DOMAIN), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_ENTITIES): cv.entities_domain(DOMAIN), + } +) -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 Group Cover platform.""" - async_add_entities( - [CoverGroup(config[CONF_NAME], config[CONF_ENTITIES])]) + async_add_entities([CoverGroup(config[CONF_NAME], config[CONF_ENTITIES])]) class CoverGroup(CoverDevice): @@ -59,14 +80,13 @@ class CoverGroup(CoverDevice): self._assumed_state = True self._entities = entities - self._covers = {KEY_OPEN_CLOSE: set(), KEY_STOP: set(), - KEY_POSITION: set()} - self._tilts = {KEY_OPEN_CLOSE: set(), KEY_STOP: set(), - KEY_POSITION: set()} + self._covers = {KEY_OPEN_CLOSE: set(), KEY_STOP: set(), KEY_POSITION: set()} + self._tilts = {KEY_OPEN_CLOSE: set(), KEY_STOP: set(), KEY_POSITION: set()} @callback - def update_supported_features(self, entity_id, old_state, new_state, - update_state=True): + def update_supported_features( + self, entity_id, old_state, new_state, update_state=True + ): """Update dictionaries with supported features.""" if not new_state: for values in self._covers.values(): @@ -112,10 +132,12 @@ class CoverGroup(CoverDevice): """Register listeners.""" for entity_id in self._entities: new_state = self.hass.states.get(entity_id) - self.update_supported_features(entity_id, None, new_state, - update_state=False) - async_track_state_change(self.hass, self._entities, - self.update_supported_features) + self.update_supported_features( + entity_id, None, new_state, update_state=False + ) + async_track_state_change( + self.hass, self._entities, self.update_supported_features + ) await self.async_update() @property @@ -157,51 +179,63 @@ class CoverGroup(CoverDevice): """Move the covers up.""" data = {ATTR_ENTITY_ID: self._covers[KEY_OPEN_CLOSE]} await self.hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER, data, blocking=True) + DOMAIN, SERVICE_OPEN_COVER, data, blocking=True + ) async def async_close_cover(self, **kwargs): """Move the covers down.""" data = {ATTR_ENTITY_ID: self._covers[KEY_OPEN_CLOSE]} await self.hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER, data, blocking=True) + DOMAIN, SERVICE_CLOSE_COVER, data, blocking=True + ) async def async_stop_cover(self, **kwargs): """Fire the stop action.""" data = {ATTR_ENTITY_ID: self._covers[KEY_STOP]} await self.hass.services.async_call( - DOMAIN, SERVICE_STOP_COVER, data, blocking=True) + DOMAIN, SERVICE_STOP_COVER, data, blocking=True + ) async def async_set_cover_position(self, **kwargs): """Set covers position.""" - data = {ATTR_ENTITY_ID: self._covers[KEY_POSITION], - ATTR_POSITION: kwargs[ATTR_POSITION]} + data = { + ATTR_ENTITY_ID: self._covers[KEY_POSITION], + ATTR_POSITION: kwargs[ATTR_POSITION], + } await self.hass.services.async_call( - DOMAIN, SERVICE_SET_COVER_POSITION, data, blocking=True) + DOMAIN, SERVICE_SET_COVER_POSITION, data, blocking=True + ) async def async_open_cover_tilt(self, **kwargs): """Tilt covers open.""" data = {ATTR_ENTITY_ID: self._tilts[KEY_OPEN_CLOSE]} await self.hass.services.async_call( - DOMAIN, SERVICE_OPEN_COVER_TILT, data, blocking=True) + DOMAIN, SERVICE_OPEN_COVER_TILT, data, blocking=True + ) async def async_close_cover_tilt(self, **kwargs): """Tilt covers closed.""" data = {ATTR_ENTITY_ID: self._tilts[KEY_OPEN_CLOSE]} await self.hass.services.async_call( - DOMAIN, SERVICE_CLOSE_COVER_TILT, data, blocking=True) + DOMAIN, SERVICE_CLOSE_COVER_TILT, data, blocking=True + ) async def async_stop_cover_tilt(self, **kwargs): """Stop cover tilt.""" data = {ATTR_ENTITY_ID: self._tilts[KEY_STOP]} await self.hass.services.async_call( - DOMAIN, SERVICE_STOP_COVER_TILT, data, blocking=True) + DOMAIN, SERVICE_STOP_COVER_TILT, data, blocking=True + ) async def async_set_cover_tilt_position(self, **kwargs): """Set tilt position.""" - data = {ATTR_ENTITY_ID: self._tilts[KEY_POSITION], - ATTR_TILT_POSITION: kwargs[ATTR_TILT_POSITION]} + data = { + ATTR_ENTITY_ID: self._tilts[KEY_POSITION], + ATTR_TILT_POSITION: kwargs[ATTR_TILT_POSITION], + } await self.hass.services.async_call( - DOMAIN, SERVICE_SET_COVER_TILT_POSITION, data, blocking=True) + DOMAIN, SERVICE_SET_COVER_TILT_POSITION, data, blocking=True + ) async def async_update(self): """Update state and attributes.""" @@ -249,18 +283,18 @@ class CoverGroup(CoverDevice): self._tilt_position = position supported_features = 0 - supported_features |= SUPPORT_OPEN | SUPPORT_CLOSE \ - if self._covers[KEY_OPEN_CLOSE] else 0 - supported_features |= SUPPORT_STOP \ - if self._covers[KEY_STOP] else 0 - supported_features |= SUPPORT_SET_POSITION \ - if self._covers[KEY_POSITION] else 0 - supported_features |= SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT \ - if self._tilts[KEY_OPEN_CLOSE] else 0 - supported_features |= SUPPORT_STOP_TILT \ - if self._tilts[KEY_STOP] else 0 - supported_features |= SUPPORT_SET_TILT_POSITION \ - if self._tilts[KEY_POSITION] else 0 + supported_features |= ( + SUPPORT_OPEN | SUPPORT_CLOSE if self._covers[KEY_OPEN_CLOSE] else 0 + ) + supported_features |= SUPPORT_STOP if self._covers[KEY_STOP] else 0 + supported_features |= SUPPORT_SET_POSITION if self._covers[KEY_POSITION] else 0 + supported_features |= ( + SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT if self._tilts[KEY_OPEN_CLOSE] else 0 + ) + supported_features |= SUPPORT_STOP_TILT if self._tilts[KEY_STOP] else 0 + supported_features |= ( + SUPPORT_SET_TILT_POSITION if self._tilts[KEY_POSITION] else 0 + ) self._supported_features = supported_features if not self._assumed_state: diff --git a/homeassistant/components/cover/homematic.py b/homeassistant/components/cover/homematic.py index 935743212..e812b7a2a 100644 --- a/homeassistant/components/cover/homematic.py +++ b/homeassistant/components/cover/homematic.py @@ -7,13 +7,16 @@ https://home-assistant.io/components/cover.homematic/ import logging from homeassistant.components.cover import ( - ATTR_POSITION, ATTR_TILT_POSITION, CoverDevice) + ATTR_POSITION, + ATTR_TILT_POSITION, + CoverDevice, +) from homeassistant.components.homematic import ATTR_DISCOVER_DEVICES, HMDevice from homeassistant.const import STATE_UNKNOWN _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['homematic'] +DEPENDENCIES = ["homematic"] def setup_platform(hass, config, add_entities, discovery_info=None): @@ -72,8 +75,7 @@ class HMCover(HMDevice, CoverDevice): self._state = "LEVEL" self._data.update({self._state: STATE_UNKNOWN}) if "LEVEL_2" in self._hmdevice.WRITENODE: - self._data.update( - {'LEVEL_2': STATE_UNKNOWN}) + self._data.update({"LEVEL_2": STATE_UNKNOWN}) @property def current_cover_tilt_position(self): @@ -81,10 +83,10 @@ class HMCover(HMDevice, CoverDevice): None is unknown, 0 is closed, 100 is fully open. """ - if 'LEVEL_2' not in self._data: + if "LEVEL_2" not in self._data: return None - return int(self._data.get('LEVEL_2', 0) * 100) + return int(self._data.get("LEVEL_2", 0) * 100) def set_cover_tilt_position(self, **kwargs): """Move the cover tilt to a specific position.""" diff --git a/homeassistant/components/cover/insteon.py b/homeassistant/components/cover/insteon.py index f0cf93c13..f98efe5c5 100644 --- a/homeassistant/components/cover/insteon.py +++ b/homeassistant/components/cover/insteon.py @@ -8,30 +8,36 @@ 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) +from homeassistant.components.cover import ( + CoverDevice, + ATTR_POSITION, + SUPPORT_OPEN, + SUPPORT_CLOSE, + SUPPORT_SET_POSITION, +) _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['insteon'] +DEPENDENCIES = ["insteon"] SUPPORTED_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION -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 Insteon platform.""" if not discovery_info: return - insteon_modem = hass.data['insteon'].get('modem') + insteon_modem = hass.data["insteon"].get("modem") - address = discovery_info['address'] + address = discovery_info["address"] device = insteon_modem.devices[address] - state_key = discovery_info['state_key'] + state_key = discovery_info["state_key"] - _LOGGER.debug('Adding device %s entity %s to Cover platform', - device.address.hex, device.states[state_key].name) + _LOGGER.debug( + "Adding device %s entity %s to Cover platform", + device.address.hex, + device.states[state_key].name, + ) new_entity = InsteonCoverDevice(device, state_key) @@ -44,7 +50,7 @@ class InsteonCoverDevice(InsteonEntity, CoverDevice): @property def current_cover_position(self): """Return the current cover position.""" - return int(math.ceil(self._insteon_device_state.value*100/255)) + return int(math.ceil(self._insteon_device_state.value * 100 / 255)) @property def supported_features(self): @@ -66,7 +72,7 @@ class InsteonCoverDevice(InsteonEntity, CoverDevice): async def async_set_cover_position(self, **kwargs): """Set the cover position.""" - position = int(kwargs[ATTR_POSITION]*255/100) + position = int(kwargs[ATTR_POSITION] * 255 / 100) if position == 0: self._insteon_device_state.close() else: diff --git a/homeassistant/components/cover/isy994.py b/homeassistant/components/cover/isy994.py index 428c1f326..f0e533f7f 100644 --- a/homeassistant/components/cover/isy994.py +++ b/homeassistant/components/cover/isy994.py @@ -8,10 +8,14 @@ import logging from typing import Callable from homeassistant.components.cover import CoverDevice, DOMAIN -from homeassistant.components.isy994 import (ISY994_NODES, ISY994_PROGRAMS, - ISYDevice) +from homeassistant.components.isy994 import ISY994_NODES, ISY994_PROGRAMS, ISYDevice from homeassistant.const import ( - STATE_OPEN, STATE_CLOSED, STATE_OPENING, STATE_CLOSING, STATE_UNKNOWN) + STATE_OPEN, + STATE_CLOSED, + STATE_OPENING, + STATE_CLOSING, + STATE_UNKNOWN, +) from homeassistant.helpers.typing import ConfigType _LOGGER = logging.getLogger(__name__) @@ -19,14 +23,15 @@ _LOGGER = logging.getLogger(__name__) VALUE_TO_STATE = { 0: STATE_CLOSED, 101: STATE_UNKNOWN, - 102: 'stopped', + 102: "stopped", 103: STATE_CLOSING, - 104: STATE_OPENING + 104: STATE_OPENING, } -def setup_platform(hass, config: ConfigType, - add_entities: Callable[[list], None], discovery_info=None): +def setup_platform( + hass, config: ConfigType, add_entities: Callable[[list], None], discovery_info=None +): """Set up the ISY994 cover platform.""" devices = [] for node in hass.data[ISY994_NODES][DOMAIN]: diff --git a/homeassistant/components/cover/knx.py b/homeassistant/components/cover/knx.py index 43a87fab3..1c2a5ba58 100644 --- a/homeassistant/components/cover/knx.py +++ b/homeassistant/components/cover/knx.py @@ -8,49 +8,59 @@ https://home-assistant.io/components/cover.knx/ import voluptuous as vol from homeassistant.components.cover import ( - ATTR_POSITION, ATTR_TILT_POSITION, PLATFORM_SCHEMA, SUPPORT_CLOSE, - SUPPORT_OPEN, SUPPORT_SET_POSITION, SUPPORT_SET_TILT_POSITION, - SUPPORT_STOP, CoverDevice) + ATTR_POSITION, + ATTR_TILT_POSITION, + PLATFORM_SCHEMA, + SUPPORT_CLOSE, + SUPPORT_OPEN, + SUPPORT_SET_POSITION, + SUPPORT_SET_TILT_POSITION, + SUPPORT_STOP, + CoverDevice, +) from homeassistant.components.knx import ATTR_DISCOVER_DEVICES, DATA_KNX from homeassistant.const import CONF_NAME from homeassistant.core import callback import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_utc_time_change -CONF_MOVE_LONG_ADDRESS = 'move_long_address' -CONF_MOVE_SHORT_ADDRESS = 'move_short_address' -CONF_POSITION_ADDRESS = 'position_address' -CONF_POSITION_STATE_ADDRESS = 'position_state_address' -CONF_ANGLE_ADDRESS = 'angle_address' -CONF_ANGLE_STATE_ADDRESS = 'angle_state_address' -CONF_TRAVELLING_TIME_DOWN = 'travelling_time_down' -CONF_TRAVELLING_TIME_UP = 'travelling_time_up' -CONF_INVERT_POSITION = 'invert_position' -CONF_INVERT_ANGLE = 'invert_angle' +CONF_MOVE_LONG_ADDRESS = "move_long_address" +CONF_MOVE_SHORT_ADDRESS = "move_short_address" +CONF_POSITION_ADDRESS = "position_address" +CONF_POSITION_STATE_ADDRESS = "position_state_address" +CONF_ANGLE_ADDRESS = "angle_address" +CONF_ANGLE_STATE_ADDRESS = "angle_state_address" +CONF_TRAVELLING_TIME_DOWN = "travelling_time_down" +CONF_TRAVELLING_TIME_UP = "travelling_time_up" +CONF_INVERT_POSITION = "invert_position" +CONF_INVERT_ANGLE = "invert_angle" DEFAULT_TRAVEL_TIME = 25 -DEFAULT_NAME = 'KNX Cover' -DEPENDENCIES = ['knx'] +DEFAULT_NAME = "KNX Cover" +DEPENDENCIES = ["knx"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_MOVE_LONG_ADDRESS): cv.string, - vol.Optional(CONF_MOVE_SHORT_ADDRESS): cv.string, - vol.Optional(CONF_POSITION_ADDRESS): cv.string, - vol.Optional(CONF_POSITION_STATE_ADDRESS): cv.string, - vol.Optional(CONF_ANGLE_ADDRESS): cv.string, - vol.Optional(CONF_ANGLE_STATE_ADDRESS): cv.string, - vol.Optional(CONF_TRAVELLING_TIME_DOWN, default=DEFAULT_TRAVEL_TIME): - cv.positive_int, - vol.Optional(CONF_TRAVELLING_TIME_UP, default=DEFAULT_TRAVEL_TIME): - cv.positive_int, - vol.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, - vol.Optional(CONF_INVERT_ANGLE, default=False): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_MOVE_LONG_ADDRESS): cv.string, + vol.Optional(CONF_MOVE_SHORT_ADDRESS): cv.string, + vol.Optional(CONF_POSITION_ADDRESS): cv.string, + vol.Optional(CONF_POSITION_STATE_ADDRESS): cv.string, + vol.Optional(CONF_ANGLE_ADDRESS): cv.string, + vol.Optional(CONF_ANGLE_STATE_ADDRESS): cv.string, + vol.Optional( + CONF_TRAVELLING_TIME_DOWN, default=DEFAULT_TRAVEL_TIME + ): cv.positive_int, + vol.Optional( + CONF_TRAVELLING_TIME_UP, default=DEFAULT_TRAVEL_TIME + ): cv.positive_int, + vol.Optional(CONF_INVERT_POSITION, default=False): cv.boolean, + vol.Optional(CONF_INVERT_ANGLE, default=False): cv.boolean, + } +) -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 cover(s) for KNX platform.""" if discovery_info is not None: async_add_entities_discovery(hass, discovery_info, async_add_entities) @@ -72,20 +82,21 @@ def async_add_entities_discovery(hass, discovery_info, async_add_entities): def async_add_entities_config(hass, config, async_add_entities): """Set up cover for KNX platform configured within platform.""" import xknx + cover = xknx.devices.Cover( hass.data[DATA_KNX].xknx, name=config.get(CONF_NAME), group_address_long=config.get(CONF_MOVE_LONG_ADDRESS), group_address_short=config.get(CONF_MOVE_SHORT_ADDRESS), - group_address_position_state=config.get( - CONF_POSITION_STATE_ADDRESS), + group_address_position_state=config.get(CONF_POSITION_STATE_ADDRESS), group_address_angle=config.get(CONF_ANGLE_ADDRESS), group_address_angle_state=config.get(CONF_ANGLE_STATE_ADDRESS), group_address_position=config.get(CONF_POSITION_ADDRESS), travel_time_down=config.get(CONF_TRAVELLING_TIME_DOWN), travel_time_up=config.get(CONF_TRAVELLING_TIME_UP), invert_position=config.get(CONF_INVERT_POSITION), - invert_angle=config.get(CONF_INVERT_ANGLE)) + invert_angle=config.get(CONF_INVERT_ANGLE), + ) hass.data[DATA_KNX].xknx.devices.add(cover) async_add_entities([KNXCover(hass, cover)]) @@ -105,9 +116,11 @@ class KNXCover(CoverDevice): @callback def async_register_callbacks(self): """Register callbacks to update hass after device was changed.""" + async def after_update_callback(device): """Call after device was updated.""" await self.async_update_ha_state() + self.device.register_device_updated_cb(after_update_callback) @property @@ -128,8 +141,9 @@ class KNXCover(CoverDevice): @property def supported_features(self): """Flag supported features.""" - supported_features = SUPPORT_OPEN | SUPPORT_CLOSE | \ - SUPPORT_SET_POSITION | SUPPORT_STOP + supported_features = ( + SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_SET_POSITION | SUPPORT_STOP + ) if self.device.supports_angle: supported_features |= SUPPORT_SET_TILT_POSITION return supported_features @@ -185,7 +199,8 @@ class KNXCover(CoverDevice): """Start the autoupdater to update HASS while cover is moving.""" if self._unsubscribe_auto_updater is None: self._unsubscribe_auto_updater = async_track_utc_time_change( - self.hass, self.auto_updater_hook) + self.hass, self.auto_updater_hook + ) def stop_auto_updater(self): """Stop the autoupdater.""" diff --git a/homeassistant/components/cover/lutron.py b/homeassistant/components/cover/lutron.py index 7ea7abf88..e53b8ad77 100644 --- a/homeassistant/components/cover/lutron.py +++ b/homeassistant/components/cover/lutron.py @@ -7,20 +7,27 @@ https://home-assistant.io/components/cover.lutron/ import logging from homeassistant.components.cover import ( - CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_SET_POSITION, - ATTR_POSITION) + CoverDevice, + SUPPORT_OPEN, + SUPPORT_CLOSE, + SUPPORT_SET_POSITION, + ATTR_POSITION, +) from homeassistant.components.lutron import ( - LutronDevice, LUTRON_DEVICES, LUTRON_CONTROLLER) + LutronDevice, + LUTRON_DEVICES, + LUTRON_CONTROLLER, +) _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['lutron'] +DEPENDENCIES = ["lutron"] def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Lutron shades.""" devs = [] - for (area_name, device) in hass.data[LUTRON_DEVICES]['cover']: + for (area_name, device) in hass.data[LUTRON_DEVICES]["cover"]: dev = LutronCover(area_name, device, hass.data[LUTRON_CONTROLLER]) devs.append(dev) @@ -64,12 +71,11 @@ class LutronCover(LutronDevice, CoverDevice): """Call when forcing a refresh of the device.""" # Reading the property (rather than last_level()) fetches value level = self._lutron_device.level - _LOGGER.debug("Lutron ID: %d updated to %f", - self._lutron_device.id, level) + _LOGGER.debug("Lutron ID: %d updated to %f", self._lutron_device.id, level) @property def device_state_attributes(self): """Return the state attributes.""" attr = {} - attr['Lutron Integration ID'] = self._lutron_device.id + attr["Lutron Integration ID"] = self._lutron_device.id return attr diff --git a/homeassistant/components/cover/lutron_caseta.py b/homeassistant/components/cover/lutron_caseta.py index 37b7c1be4..46cd45579 100644 --- a/homeassistant/components/cover/lutron_caseta.py +++ b/homeassistant/components/cover/lutron_caseta.py @@ -7,18 +7,24 @@ https://home-assistant.io/components/cover.lutron_caseta/ import logging from homeassistant.components.cover import ( - CoverDevice, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_SET_POSITION, - ATTR_POSITION, DOMAIN) + CoverDevice, + SUPPORT_OPEN, + SUPPORT_CLOSE, + SUPPORT_SET_POSITION, + ATTR_POSITION, + DOMAIN, +) from homeassistant.components.lutron_caseta import ( - LUTRON_CASETA_SMARTBRIDGE, LutronCasetaDevice) + LUTRON_CASETA_SMARTBRIDGE, + LutronCasetaDevice, +) _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['lutron_caseta'] +DEPENDENCIES = ["lutron_caseta"] -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 Lutron Caseta shades as a cover device.""" devs = [] bridge = hass.data[LUTRON_CASETA_SMARTBRIDGE] @@ -41,12 +47,12 @@ class LutronCasetaCover(LutronCasetaDevice, CoverDevice): @property def is_closed(self): """Return if the cover is closed.""" - return self._state['current_state'] < 1 + return self._state["current_state"] < 1 @property def current_cover_position(self): """Return the current position of cover.""" - return self._state['current_state'] + return self._state["current_state"] async def async_close_cover(self, **kwargs): """Close the cover.""" diff --git a/homeassistant/components/cover/mqtt.py b/homeassistant/components/cover/mqtt.py index 977353cb3..d484c37df 100644 --- a/homeassistant/components/cover/mqtt.py +++ b/homeassistant/components/cover/mqtt.py @@ -11,45 +11,66 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.components import mqtt from homeassistant.components.cover import ( - CoverDevice, ATTR_TILT_POSITION, SUPPORT_OPEN_TILT, - SUPPORT_CLOSE_TILT, SUPPORT_STOP_TILT, SUPPORT_SET_TILT_POSITION, - SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP, SUPPORT_SET_POSITION, - ATTR_POSITION) + CoverDevice, + ATTR_TILT_POSITION, + SUPPORT_OPEN_TILT, + SUPPORT_CLOSE_TILT, + SUPPORT_STOP_TILT, + SUPPORT_SET_TILT_POSITION, + SUPPORT_OPEN, + SUPPORT_CLOSE, + SUPPORT_STOP, + SUPPORT_SET_POSITION, + ATTR_POSITION, +) from homeassistant.exceptions import TemplateError from homeassistant.const import ( - CONF_NAME, CONF_VALUE_TEMPLATE, CONF_OPTIMISTIC, STATE_OPEN, - STATE_CLOSED, STATE_UNKNOWN) + CONF_NAME, + CONF_VALUE_TEMPLATE, + CONF_OPTIMISTIC, + STATE_OPEN, + STATE_CLOSED, + STATE_UNKNOWN, +) from homeassistant.components.mqtt import ( - CONF_AVAILABILITY_TOPIC, CONF_STATE_TOPIC, CONF_COMMAND_TOPIC, - CONF_PAYLOAD_AVAILABLE, CONF_PAYLOAD_NOT_AVAILABLE, CONF_QOS, CONF_RETAIN, - valid_publish_topic, valid_subscribe_topic, MqttAvailability) + CONF_AVAILABILITY_TOPIC, + CONF_STATE_TOPIC, + CONF_COMMAND_TOPIC, + CONF_PAYLOAD_AVAILABLE, + CONF_PAYLOAD_NOT_AVAILABLE, + CONF_QOS, + CONF_RETAIN, + valid_publish_topic, + valid_subscribe_topic, + MqttAvailability, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['mqtt'] +DEPENDENCIES = ["mqtt"] -CONF_TILT_COMMAND_TOPIC = 'tilt_command_topic' -CONF_TILT_STATUS_TOPIC = 'tilt_status_topic' -CONF_POSITION_TOPIC = 'set_position_topic' -CONF_SET_POSITION_TEMPLATE = 'set_position_template' +CONF_TILT_COMMAND_TOPIC = "tilt_command_topic" +CONF_TILT_STATUS_TOPIC = "tilt_status_topic" +CONF_POSITION_TOPIC = "set_position_topic" +CONF_SET_POSITION_TEMPLATE = "set_position_template" -CONF_PAYLOAD_OPEN = 'payload_open' -CONF_PAYLOAD_CLOSE = 'payload_close' -CONF_PAYLOAD_STOP = 'payload_stop' -CONF_STATE_OPEN = 'state_open' -CONF_STATE_CLOSED = 'state_closed' -CONF_TILT_CLOSED_POSITION = 'tilt_closed_value' -CONF_TILT_OPEN_POSITION = 'tilt_opened_value' -CONF_TILT_MIN = 'tilt_min' -CONF_TILT_MAX = 'tilt_max' -CONF_TILT_STATE_OPTIMISTIC = 'tilt_optimistic' -CONF_TILT_INVERT_STATE = 'tilt_invert_state' +CONF_PAYLOAD_OPEN = "payload_open" +CONF_PAYLOAD_CLOSE = "payload_close" +CONF_PAYLOAD_STOP = "payload_stop" +CONF_STATE_OPEN = "state_open" +CONF_STATE_CLOSED = "state_closed" +CONF_TILT_CLOSED_POSITION = "tilt_closed_value" +CONF_TILT_OPEN_POSITION = "tilt_opened_value" +CONF_TILT_MIN = "tilt_min" +CONF_TILT_MAX = "tilt_max" +CONF_TILT_STATE_OPTIMISTIC = "tilt_optimistic" +CONF_TILT_INVERT_STATE = "tilt_invert_state" -DEFAULT_NAME = 'MQTT Cover' -DEFAULT_PAYLOAD_OPEN = 'OPEN' -DEFAULT_PAYLOAD_CLOSE = 'CLOSE' -DEFAULT_PAYLOAD_STOP = 'STOP' +DEFAULT_NAME = "MQTT Cover" +DEFAULT_PAYLOAD_OPEN = "OPEN" +DEFAULT_PAYLOAD_CLOSE = "CLOSE" +DEFAULT_PAYLOAD_STOP = "STOP" DEFAULT_OPTIMISTIC = False DEFAULT_RETAIN = False DEFAULT_TILT_CLOSED_POSITION = 0 @@ -59,41 +80,48 @@ DEFAULT_TILT_MAX = 100 DEFAULT_TILT_OPTIMISTIC = False DEFAULT_TILT_INVERT_STATE = False -OPEN_CLOSE_FEATURES = (SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP) -TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT | - SUPPORT_SET_TILT_POSITION) +OPEN_CLOSE_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE | SUPPORT_STOP +TILT_FEATURES = ( + SUPPORT_OPEN_TILT + | SUPPORT_CLOSE_TILT + | SUPPORT_STOP_TILT + | SUPPORT_SET_TILT_POSITION +) -PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_COMMAND_TOPIC): valid_publish_topic, - vol.Optional(CONF_POSITION_TOPIC): valid_publish_topic, - vol.Optional(CONF_SET_POSITION_TEMPLATE): cv.template, - vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, - vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic, - vol.Optional(CONF_VALUE_TEMPLATE): cv.template, - vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, - vol.Optional(CONF_PAYLOAD_OPEN, default=DEFAULT_PAYLOAD_OPEN): cv.string, - vol.Optional(CONF_PAYLOAD_CLOSE, default=DEFAULT_PAYLOAD_CLOSE): cv.string, - vol.Optional(CONF_PAYLOAD_STOP, default=DEFAULT_PAYLOAD_STOP): cv.string, - vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string, - vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string, - vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_TILT_COMMAND_TOPIC): valid_publish_topic, - vol.Optional(CONF_TILT_STATUS_TOPIC): valid_subscribe_topic, - vol.Optional(CONF_TILT_CLOSED_POSITION, - default=DEFAULT_TILT_CLOSED_POSITION): int, - vol.Optional(CONF_TILT_OPEN_POSITION, - default=DEFAULT_TILT_OPEN_POSITION): int, - vol.Optional(CONF_TILT_MIN, default=DEFAULT_TILT_MIN): int, - vol.Optional(CONF_TILT_MAX, default=DEFAULT_TILT_MAX): int, - vol.Optional(CONF_TILT_STATE_OPTIMISTIC, - default=DEFAULT_TILT_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_TILT_INVERT_STATE, - default=DEFAULT_TILT_INVERT_STATE): cv.boolean, -}).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) +PLATFORM_SCHEMA = mqtt.MQTT_BASE_PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_COMMAND_TOPIC): valid_publish_topic, + vol.Optional(CONF_POSITION_TOPIC): valid_publish_topic, + vol.Optional(CONF_SET_POSITION_TEMPLATE): cv.template, + vol.Optional(CONF_RETAIN, default=DEFAULT_RETAIN): cv.boolean, + vol.Optional(CONF_STATE_TOPIC): valid_subscribe_topic, + vol.Optional(CONF_VALUE_TEMPLATE): cv.template, + vol.Optional(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Optional(CONF_PAYLOAD_OPEN, default=DEFAULT_PAYLOAD_OPEN): cv.string, + vol.Optional(CONF_PAYLOAD_CLOSE, default=DEFAULT_PAYLOAD_CLOSE): cv.string, + vol.Optional(CONF_PAYLOAD_STOP, default=DEFAULT_PAYLOAD_STOP): cv.string, + vol.Optional(CONF_STATE_OPEN, default=STATE_OPEN): cv.string, + vol.Optional(CONF_STATE_CLOSED, default=STATE_CLOSED): cv.string, + vol.Optional(CONF_OPTIMISTIC, default=DEFAULT_OPTIMISTIC): cv.boolean, + vol.Optional(CONF_TILT_COMMAND_TOPIC): valid_publish_topic, + vol.Optional(CONF_TILT_STATUS_TOPIC): valid_subscribe_topic, + vol.Optional( + CONF_TILT_CLOSED_POSITION, default=DEFAULT_TILT_CLOSED_POSITION + ): int, + vol.Optional(CONF_TILT_OPEN_POSITION, default=DEFAULT_TILT_OPEN_POSITION): int, + vol.Optional(CONF_TILT_MIN, default=DEFAULT_TILT_MIN): int, + vol.Optional(CONF_TILT_MAX, default=DEFAULT_TILT_MAX): int, + vol.Optional( + CONF_TILT_STATE_OPTIMISTIC, default=DEFAULT_TILT_OPTIMISTIC + ): cv.boolean, + vol.Optional( + CONF_TILT_INVERT_STATE, default=DEFAULT_TILT_INVERT_STATE + ): cv.boolean, + } +).extend(mqtt.MQTT_AVAILABILITY_SCHEMA.schema) -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 MQTT Cover.""" if discovery_info is not None: config = PLATFORM_SCHEMA(discovery_info) @@ -105,48 +133,74 @@ async def async_setup_platform(hass, config, async_add_entities, if set_position_template is not None: set_position_template.hass = hass - async_add_entities([MqttCover( - config.get(CONF_NAME), - config.get(CONF_STATE_TOPIC), - config.get(CONF_COMMAND_TOPIC), - config.get(CONF_AVAILABILITY_TOPIC), - config.get(CONF_TILT_COMMAND_TOPIC), - config.get(CONF_TILT_STATUS_TOPIC), - config.get(CONF_QOS), - config.get(CONF_RETAIN), - config.get(CONF_STATE_OPEN), - config.get(CONF_STATE_CLOSED), - config.get(CONF_PAYLOAD_OPEN), - config.get(CONF_PAYLOAD_CLOSE), - config.get(CONF_PAYLOAD_STOP), - config.get(CONF_PAYLOAD_AVAILABLE), - config.get(CONF_PAYLOAD_NOT_AVAILABLE), - config.get(CONF_OPTIMISTIC), - value_template, - config.get(CONF_TILT_OPEN_POSITION), - config.get(CONF_TILT_CLOSED_POSITION), - config.get(CONF_TILT_MIN), - config.get(CONF_TILT_MAX), - config.get(CONF_TILT_STATE_OPTIMISTIC), - config.get(CONF_TILT_INVERT_STATE), - config.get(CONF_POSITION_TOPIC), - set_position_template, - )]) + async_add_entities( + [ + MqttCover( + config.get(CONF_NAME), + config.get(CONF_STATE_TOPIC), + config.get(CONF_COMMAND_TOPIC), + config.get(CONF_AVAILABILITY_TOPIC), + config.get(CONF_TILT_COMMAND_TOPIC), + config.get(CONF_TILT_STATUS_TOPIC), + config.get(CONF_QOS), + config.get(CONF_RETAIN), + config.get(CONF_STATE_OPEN), + config.get(CONF_STATE_CLOSED), + config.get(CONF_PAYLOAD_OPEN), + config.get(CONF_PAYLOAD_CLOSE), + config.get(CONF_PAYLOAD_STOP), + config.get(CONF_PAYLOAD_AVAILABLE), + config.get(CONF_PAYLOAD_NOT_AVAILABLE), + config.get(CONF_OPTIMISTIC), + value_template, + config.get(CONF_TILT_OPEN_POSITION), + config.get(CONF_TILT_CLOSED_POSITION), + config.get(CONF_TILT_MIN), + config.get(CONF_TILT_MAX), + config.get(CONF_TILT_STATE_OPTIMISTIC), + config.get(CONF_TILT_INVERT_STATE), + config.get(CONF_POSITION_TOPIC), + set_position_template, + ) + ] + ) class MqttCover(MqttAvailability, CoverDevice): """Representation of a cover that can be controlled using MQTT.""" - def __init__(self, name, state_topic, command_topic, availability_topic, - tilt_command_topic, tilt_status_topic, qos, retain, - state_open, state_closed, payload_open, payload_close, - payload_stop, payload_available, payload_not_available, - optimistic, value_template, tilt_open_position, - tilt_closed_position, tilt_min, tilt_max, tilt_optimistic, - tilt_invert, position_topic, set_position_template): + def __init__( + self, + name, + state_topic, + command_topic, + availability_topic, + tilt_command_topic, + tilt_status_topic, + qos, + retain, + state_open, + state_closed, + payload_open, + payload_close, + payload_stop, + payload_available, + payload_not_available, + optimistic, + value_template, + tilt_open_position, + tilt_closed_position, + tilt_min, + tilt_max, + tilt_optimistic, + tilt_invert, + position_topic, + set_position_template, + ): """Initialize the cover.""" - super().__init__(availability_topic, qos, payload_available, - payload_not_available) + super().__init__( + availability_topic, qos, payload_available, payload_not_available + ) self._position = None self._state = None self._name = name @@ -180,8 +234,7 @@ class MqttCover(MqttAvailability, CoverDevice): @callback def tilt_updated(topic, payload, qos): """Handle tilt updates.""" - if (payload.isnumeric() and - self._tilt_min <= int(payload) <= self._tilt_max): + if payload.isnumeric() and self._tilt_min <= int(payload) <= self._tilt_max: level = self.find_percentage_in_range(float(payload)) self._tilt_value = level @@ -191,8 +244,7 @@ class MqttCover(MqttAvailability, CoverDevice): def state_message_received(topic, payload, qos): """Handle new MQTT state messages.""" if self._template is not None: - payload = self._template.async_render_with_possible_json_value( - payload) + payload = self._template.async_render_with_possible_json_value(payload) if payload == self._state_open: self._state = False @@ -206,8 +258,8 @@ class MqttCover(MqttAvailability, CoverDevice): self._position = int(payload) else: _LOGGER.warning( - "Payload is not True, False, or integer (0-100): %s", - payload) + "Payload is not True, False, or integer (0-100): %s", payload + ) return self.async_schedule_update_ha_state() @@ -217,8 +269,8 @@ class MqttCover(MqttAvailability, CoverDevice): self._optimistic = True else: await mqtt.async_subscribe( - self.hass, self._state_topic, - state_message_received, self._qos) + self.hass, self._state_topic, state_message_received, self._qos + ) if self._tilt_status_topic is None: self._tilt_optimistic = True @@ -226,7 +278,8 @@ class MqttCover(MqttAvailability, CoverDevice): self._tilt_optimistic = False self._tilt_value = STATE_UNKNOWN await mqtt.async_subscribe( - self.hass, self._tilt_status_topic, tilt_updated, self._qos) + self.hass, self._tilt_status_topic, tilt_updated, self._qos + ) @property def should_poll(self): @@ -282,8 +335,8 @@ class MqttCover(MqttAvailability, CoverDevice): This method is a coroutine. """ mqtt.async_publish( - self.hass, self._command_topic, self._payload_open, self._qos, - self._retain) + self.hass, self._command_topic, self._payload_open, self._qos, self._retain + ) if self._optimistic: # Optimistically assume that cover has changed state. self._state = False @@ -295,8 +348,8 @@ class MqttCover(MqttAvailability, CoverDevice): This method is a coroutine. """ mqtt.async_publish( - self.hass, self._command_topic, self._payload_close, self._qos, - self._retain) + self.hass, self._command_topic, self._payload_close, self._qos, self._retain + ) if self._optimistic: # Optimistically assume that cover has changed state. self._state = True @@ -308,23 +361,31 @@ class MqttCover(MqttAvailability, CoverDevice): This method is a coroutine. """ mqtt.async_publish( - self.hass, self._command_topic, self._payload_stop, self._qos, - self._retain) + self.hass, self._command_topic, self._payload_stop, self._qos, self._retain + ) async def async_open_cover_tilt(self, **kwargs): """Tilt the cover open.""" - mqtt.async_publish(self.hass, self._tilt_command_topic, - self._tilt_open_position, self._qos, - self._retain) + mqtt.async_publish( + self.hass, + self._tilt_command_topic, + self._tilt_open_position, + self._qos, + self._retain, + ) if self._tilt_optimistic: self._tilt_value = self._tilt_open_position self.async_schedule_update_ha_state() async def async_close_cover_tilt(self, **kwargs): """Tilt the cover closed.""" - mqtt.async_publish(self.hass, self._tilt_command_topic, - self._tilt_closed_position, self._qos, - self._retain) + mqtt.async_publish( + self.hass, + self._tilt_command_topic, + self._tilt_closed_position, + self._qos, + self._retain, + ) if self._tilt_optimistic: self._tilt_value = self._tilt_closed_position self.async_schedule_update_ha_state() @@ -339,8 +400,9 @@ class MqttCover(MqttAvailability, CoverDevice): # The position needs to be between min and max level = self.find_in_range_from_percent(position) - mqtt.async_publish(self.hass, self._tilt_command_topic, - level, self._qos, self._retain) + mqtt.async_publish( + self.hass, self._tilt_command_topic, level, self._qos, self._retain + ) async def async_set_cover_position(self, **kwargs): """Move the cover to a specific position.""" @@ -348,14 +410,14 @@ class MqttCover(MqttAvailability, CoverDevice): position = kwargs[ATTR_POSITION] if self._set_position_template is not None: try: - position = self._set_position_template.async_render( - **kwargs) + position = self._set_position_template.async_render(**kwargs) except TemplateError as ex: _LOGGER.error(ex) self._state = None - mqtt.async_publish(self.hass, self._position_topic, - position, self._qos, self._retain) + mqtt.async_publish( + self.hass, self._position_topic, position, self._qos, self._retain + ) def find_percentage_in_range(self, position): """Find the 0-100% value within the specified range.""" diff --git a/homeassistant/components/cover/myq.py b/homeassistant/components/cover/myq.py index 413794505..53c2ed259 100644 --- a/homeassistant/components/cover/myq.py +++ b/homeassistant/components/cover/myq.py @@ -8,33 +8,39 @@ import logging import voluptuous as vol -from homeassistant.components.cover import ( - CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN) +from homeassistant.components.cover import CoverDevice, SUPPORT_CLOSE, SUPPORT_OPEN from homeassistant.const import ( - CONF_PASSWORD, CONF_TYPE, CONF_USERNAME, STATE_CLOSED, STATE_CLOSING, - STATE_OPENING) + CONF_PASSWORD, + CONF_TYPE, + CONF_USERNAME, + STATE_CLOSED, + STATE_CLOSING, + STATE_OPENING, +) import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['pymyq==0.0.15'] +REQUIREMENTS = ["pymyq==0.0.15"] _LOGGER = logging.getLogger(__name__) -DEFAULT_NAME = 'myq' +DEFAULT_NAME = "myq" MYQ_TO_HASS = { - 'closed': STATE_CLOSED, - 'closing': STATE_CLOSING, - 'opening': STATE_OPENING + "closed": STATE_CLOSED, + "closing": STATE_CLOSING, + "opening": STATE_OPENING, } -NOTIFICATION_ID = 'myq_notification' -NOTIFICATION_TITLE = 'MyQ Cover Setup' +NOTIFICATION_ID = "myq_notification" +NOTIFICATION_TITLE = "MyQ Cover Setup" -COVER_SCHEMA = vol.Schema({ - vol.Required(CONF_TYPE): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string -}) +COVER_SCHEMA = vol.Schema( + { + vol.Required(CONF_TYPE): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -59,11 +65,12 @@ def setup_platform(hass, config, add_entities, discovery_info=None): except (TypeError, KeyError, NameError, ValueError) as ex: _LOGGER.error("%s", ex) hass.components.persistent_notification.create( - 'Error: {}
' - 'You will need to restart hass after fixing.' - ''.format(ex), + "Error: {}
" + "You will need to restart hass after fixing." + "".format(ex), title=NOTIFICATION_TITLE, - notification_id=NOTIFICATION_ID) + notification_id=NOTIFICATION_ID, + ) return False @@ -73,14 +80,14 @@ class MyQDevice(CoverDevice): def __init__(self, myq, device): """Initialize with API object, device id.""" self.myq = myq - self.device_id = device['deviceid'] - self._name = device['name'] + self.device_id = device["deviceid"] + self._name = device["name"] self._status = STATE_CLOSED @property def device_class(self): """Define this cover as a garage door.""" - return 'garage' + return "garage" @property def should_poll(self): diff --git a/homeassistant/components/cover/mysensors.py b/homeassistant/components/cover/mysensors.py index 60ff7aeef..ed65ecb94 100644 --- a/homeassistant/components/cover/mysensors.py +++ b/homeassistant/components/cover/mysensors.py @@ -9,12 +9,15 @@ from homeassistant.components.cover import ATTR_POSITION, DOMAIN, CoverDevice from homeassistant.const import STATE_OFF, STATE_ON -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 mysensors platform for covers.""" mysensors.setup_mysensors_platform( - hass, DOMAIN, discovery_info, MySensorsCover, - async_add_entities=async_add_entities) + hass, + DOMAIN, + discovery_info, + MySensorsCover, + async_add_entities=async_add_entities, + ) class MySensorsCover(mysensors.device.MySensorsEntity, CoverDevice): @@ -45,8 +48,7 @@ class MySensorsCover(mysensors.device.MySensorsEntity, CoverDevice): async def async_open_cover(self, **kwargs): """Move the cover up.""" set_req = self.gateway.const.SetReq - self.gateway.set_child_value( - self.node_id, self.child_id, set_req.V_UP, 1) + self.gateway.set_child_value(self.node_id, self.child_id, set_req.V_UP, 1) if self.gateway.optimistic: # Optimistically assume that cover has changed state. if set_req.V_DIMMER in self._values: @@ -58,8 +60,7 @@ class MySensorsCover(mysensors.device.MySensorsEntity, CoverDevice): async def async_close_cover(self, **kwargs): """Move the cover down.""" set_req = self.gateway.const.SetReq - self.gateway.set_child_value( - self.node_id, self.child_id, set_req.V_DOWN, 1) + self.gateway.set_child_value(self.node_id, self.child_id, set_req.V_DOWN, 1) if self.gateway.optimistic: # Optimistically assume that cover has changed state. if set_req.V_DIMMER in self._values: @@ -73,7 +74,8 @@ class MySensorsCover(mysensors.device.MySensorsEntity, CoverDevice): position = kwargs.get(ATTR_POSITION) set_req = self.gateway.const.SetReq self.gateway.set_child_value( - self.node_id, self.child_id, set_req.V_DIMMER, position) + self.node_id, self.child_id, set_req.V_DIMMER, position + ) if self.gateway.optimistic: # Optimistically assume that cover has changed state. self._values[set_req.V_DIMMER] = position @@ -82,5 +84,4 @@ class MySensorsCover(mysensors.device.MySensorsEntity, CoverDevice): async def async_stop_cover(self, **kwargs): """Stop the device.""" set_req = self.gateway.const.SetReq - self.gateway.set_child_value( - self.node_id, self.child_id, set_req.V_STOP, 1) + self.gateway.set_child_value(self.node_id, self.child_id, set_req.V_STOP, 1) diff --git a/homeassistant/components/cover/opengarage.py b/homeassistant/components/cover/opengarage.py index 19a87c5bf..d9a4c9f8e 100644 --- a/homeassistant/components/cover/opengarage.py +++ b/homeassistant/components/cover/opengarage.py @@ -10,44 +10,54 @@ import requests import voluptuous as vol from homeassistant.components.cover import ( - CoverDevice, PLATFORM_SCHEMA, SUPPORT_OPEN, SUPPORT_CLOSE) + CoverDevice, + PLATFORM_SCHEMA, + SUPPORT_OPEN, + SUPPORT_CLOSE, +) from homeassistant.const import ( - CONF_DEVICE, CONF_NAME, STATE_UNKNOWN, STATE_CLOSED, STATE_OPEN, - CONF_COVERS, CONF_HOST, CONF_PORT) + CONF_DEVICE, + CONF_NAME, + STATE_UNKNOWN, + STATE_CLOSED, + STATE_OPEN, + CONF_COVERS, + CONF_HOST, + CONF_PORT, +) import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -ATTR_DISTANCE_SENSOR = 'distance_sensor' -ATTR_DOOR_STATE = 'door_state' -ATTR_SIGNAL_STRENGTH = 'wifi_signal' +ATTR_DISTANCE_SENSOR = "distance_sensor" +ATTR_DOOR_STATE = "door_state" +ATTR_SIGNAL_STRENGTH = "wifi_signal" -CONF_DEVICE_ID = 'device_id' -CONF_DEVICE_KEY = 'device_key' +CONF_DEVICE_ID = "device_id" +CONF_DEVICE_KEY = "device_key" -DEFAULT_NAME = 'OpenGarage' +DEFAULT_NAME = "OpenGarage" DEFAULT_PORT = 80 -STATE_CLOSING = 'closing' -STATE_OFFLINE = 'offline' -STATE_OPENING = 'opening' -STATE_STOPPED = 'stopped' +STATE_CLOSING = "closing" +STATE_OFFLINE = "offline" +STATE_OPENING = "opening" +STATE_STOPPED = "stopped" -STATES_MAP = { - 0: STATE_CLOSED, - 1: STATE_OPEN, -} +STATES_MAP = {0: STATE_CLOSED, 1: STATE_OPEN} -COVER_SCHEMA = vol.Schema({ - vol.Required(CONF_DEVICE_KEY): cv.string, - vol.Required(CONF_HOST): cv.string, - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, -}) +COVER_SCHEMA = vol.Schema( + { + vol.Required(CONF_DEVICE_KEY): cv.string, + vol.Required(CONF_HOST): cv.string, + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA})} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -61,7 +71,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): CONF_HOST: device_config.get(CONF_HOST), CONF_PORT: device_config.get(CONF_PORT), CONF_DEVICE_ID: device_config.get(CONF_DEVICE, device_id), - CONF_DEVICE_KEY: device_config.get(CONF_DEVICE_KEY) + CONF_DEVICE_KEY: device_config.get(CONF_DEVICE_KEY), } covers.append(OpenGarageCover(hass, args)) @@ -74,11 +84,10 @@ class OpenGarageCover(CoverDevice): def __init__(self, hass, args): """Initialize the cover.""" - self.opengarage_url = 'http://{}:{}'.format( - args[CONF_HOST], args[CONF_PORT]) + self.opengarage_url = "http://{}:{}".format(args[CONF_HOST], args[CONF_PORT]) self.hass = hass self._name = args[CONF_NAME] - self.device_id = args['device_id'] + self.device_id = args["device_id"] self._device_key = args[CONF_DEVICE_KEY] self._state = None self._state_before_move = None @@ -138,9 +147,9 @@ class OpenGarageCover(CoverDevice): try: status = self._get_status() if self._name is None: - if status['name'] is not None: - self._name = status['name'] - state = STATES_MAP.get(status.get('door'), STATE_UNKNOWN) + if status["name"] is not None: + self._name = status["name"] + state = STATES_MAP.get(status.get("door"), STATE_UNKNOWN) if self._state_before_move is not None: if self._state_before_move != state: self._state = state @@ -149,41 +158,43 @@ class OpenGarageCover(CoverDevice): self._state = state _LOGGER.debug("%s status: %s", self._name, self._state) - self.signal = status.get('rssi') - self.dist = status.get('dist') + self.signal = status.get("rssi") + self.dist = status.get("dist") self._available = True except requests.exceptions.RequestException as ex: - _LOGGER.error("Unable to connect to OpenGarage device: %(reason)s", - dict(reason=ex)) + _LOGGER.error( + "Unable to connect to OpenGarage device: %(reason)s", dict(reason=ex) + ) self._state = STATE_OFFLINE def _get_status(self): """Get latest status.""" - url = '{}/jc'.format(self.opengarage_url) + url = "{}/jc".format(self.opengarage_url) ret = requests.get(url, timeout=10) return ret.json() def _push_button(self): """Send commands to API.""" - url = '{}/cc?dkey={}&click=1'.format( - self.opengarage_url, self._device_key) + url = "{}/cc?dkey={}&click=1".format(self.opengarage_url, self._device_key) try: response = requests.get(url, timeout=10).json() - if response['result'] == 2: - _LOGGER.error("Unable to control %s: Device key is incorrect", - self._name) + if response["result"] == 2: + _LOGGER.error( + "Unable to control %s: Device key is incorrect", self._name + ) self._state = self._state_before_move self._state_before_move = None except requests.exceptions.RequestException as ex: - _LOGGER.error("Unable to connect to OpenGarage device: %(reason)s", - dict(reason=ex)) + _LOGGER.error( + "Unable to connect to OpenGarage device: %(reason)s", dict(reason=ex) + ) self._state = self._state_before_move self._state_before_move = None @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return 'garage' + return "garage" @property def supported_features(self): diff --git a/homeassistant/components/cover/rflink.py b/homeassistant/components/cover/rflink.py index 41a4c2af0..a3cb5ae0b 100644 --- a/homeassistant/components/cover/rflink.py +++ b/homeassistant/components/cover/rflink.py @@ -9,50 +9,61 @@ import logging import voluptuous as vol from homeassistant.components.rflink import ( - DATA_ENTITY_GROUP_LOOKUP, DATA_ENTITY_LOOKUP, - DEVICE_DEFAULTS_SCHEMA, EVENT_KEY_COMMAND, RflinkCommand) -from homeassistant.components.cover import ( - CoverDevice, PLATFORM_SCHEMA) + DATA_ENTITY_GROUP_LOOKUP, + DATA_ENTITY_LOOKUP, + DEVICE_DEFAULTS_SCHEMA, + EVENT_KEY_COMMAND, + RflinkCommand, +) +from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA import homeassistant.helpers.config_validation as cv from homeassistant.const import CONF_NAME -DEPENDENCIES = ['rflink'] +DEPENDENCIES = ["rflink"] _LOGGER = logging.getLogger(__name__) -CONF_ALIASES = 'aliases' -CONF_GROUP_ALIASES = 'group_aliases' -CONF_GROUP = 'group' -CONF_NOGROUP_ALIASES = 'nogroup_aliases' -CONF_DEVICE_DEFAULTS = 'device_defaults' -CONF_DEVICES = 'devices' -CONF_AUTOMATIC_ADD = 'automatic_add' -CONF_FIRE_EVENT = 'fire_event' -CONF_IGNORE_DEVICES = 'ignore_devices' -CONF_RECONNECT_INTERVAL = 'reconnect_interval' -CONF_SIGNAL_REPETITIONS = 'signal_repetitions' -CONF_WAIT_FOR_ACK = 'wait_for_ack' +CONF_ALIASES = "aliases" +CONF_GROUP_ALIASES = "group_aliases" +CONF_GROUP = "group" +CONF_NOGROUP_ALIASES = "nogroup_aliases" +CONF_DEVICE_DEFAULTS = "device_defaults" +CONF_DEVICES = "devices" +CONF_AUTOMATIC_ADD = "automatic_add" +CONF_FIRE_EVENT = "fire_event" +CONF_IGNORE_DEVICES = "ignore_devices" +CONF_RECONNECT_INTERVAL = "reconnect_interval" +CONF_SIGNAL_REPETITIONS = "signal_repetitions" +CONF_WAIT_FOR_ACK = "wait_for_ack" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_DEVICE_DEFAULTS, default=DEVICE_DEFAULTS_SCHEMA({})): - DEVICE_DEFAULTS_SCHEMA, - vol.Optional(CONF_DEVICES, default={}): vol.Schema({ - cv.string: { - vol.Optional(CONF_NAME): cv.string, - vol.Optional(CONF_ALIASES, default=[]): - vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_GROUP_ALIASES, default=[]): - vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_NOGROUP_ALIASES, default=[]): - vol.All(cv.ensure_list, [cv.string]), - vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, - vol.Optional(CONF_SIGNAL_REPETITIONS): vol.Coerce(int), - vol.Optional(CONF_GROUP, default=True): cv.boolean, - }, - }), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional( + CONF_DEVICE_DEFAULTS, default=DEVICE_DEFAULTS_SCHEMA({}) + ): DEVICE_DEFAULTS_SCHEMA, + vol.Optional(CONF_DEVICES, default={}): vol.Schema( + { + cv.string: { + vol.Optional(CONF_NAME): cv.string, + vol.Optional(CONF_ALIASES, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional(CONF_GROUP_ALIASES, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional(CONF_NOGROUP_ALIASES, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, + vol.Optional(CONF_SIGNAL_REPETITIONS): vol.Coerce(int), + vol.Optional(CONF_GROUP, default=True): cv.boolean, + } + } + ), + } +) def devices_from_config(domain_config, hass=None): @@ -65,21 +76,18 @@ def devices_from_config(domain_config, hass=None): # Register entity (and aliases) to listen to incoming rflink events # Device id and normal aliases respond to normal and group command - hass.data[DATA_ENTITY_LOOKUP][ - EVENT_KEY_COMMAND][device_id].append(device) + hass.data[DATA_ENTITY_LOOKUP][EVENT_KEY_COMMAND][device_id].append(device) if config[CONF_GROUP]: - hass.data[DATA_ENTITY_GROUP_LOOKUP][ - EVENT_KEY_COMMAND][device_id].append(device) + hass.data[DATA_ENTITY_GROUP_LOOKUP][EVENT_KEY_COMMAND][device_id].append( + device + ) for _id in config[CONF_ALIASES]: - hass.data[DATA_ENTITY_LOOKUP][ - EVENT_KEY_COMMAND][_id].append(device) - hass.data[DATA_ENTITY_GROUP_LOOKUP][ - EVENT_KEY_COMMAND][_id].append(device) + hass.data[DATA_ENTITY_LOOKUP][EVENT_KEY_COMMAND][_id].append(device) + hass.data[DATA_ENTITY_GROUP_LOOKUP][EVENT_KEY_COMMAND][_id].append(device) return devices -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 Rflink cover platform.""" async_add_entities(devices_from_config(config, hass)) @@ -91,10 +99,10 @@ class RflinkCover(RflinkCommand, CoverDevice): """Adjust state if Rflink picks up a remote command for this device.""" self.cancel_queued_send_commands() - command = event['command'] - if command in ['on', 'allon', 'up']: + command = event["command"] + if command in ["on", "allon", "up"]: self._state = True - elif command in ['off', 'alloff', 'down']: + elif command in ["off", "alloff", "down"]: self._state = False @property diff --git a/homeassistant/components/cover/rfxtrx.py b/homeassistant/components/cover/rfxtrx.py index d486b6019..ef757a59b 100644 --- a/homeassistant/components/cover/rfxtrx.py +++ b/homeassistant/components/cover/rfxtrx.py @@ -10,23 +10,32 @@ from homeassistant.components import rfxtrx from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA from homeassistant.const import CONF_NAME from homeassistant.components.rfxtrx import ( - CONF_AUTOMATIC_ADD, CONF_FIRE_EVENT, DEFAULT_SIGNAL_REPETITIONS, - CONF_SIGNAL_REPETITIONS, CONF_DEVICES) + CONF_AUTOMATIC_ADD, + CONF_FIRE_EVENT, + DEFAULT_SIGNAL_REPETITIONS, + CONF_SIGNAL_REPETITIONS, + CONF_DEVICES, +) from homeassistant.helpers import config_validation as cv -DEPENDENCIES = ['rfxtrx'] +DEPENDENCIES = ["rfxtrx"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_DEVICES, default={}): { - cv.string: vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean - }) - }, - vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, - vol.Optional(CONF_SIGNAL_REPETITIONS, default=DEFAULT_SIGNAL_REPETITIONS): - vol.Coerce(int), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_DEVICES, default={}): { + cv.string: vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_FIRE_EVENT, default=False): cv.boolean, + } + ) + }, + vol.Optional(CONF_AUTOMATIC_ADD, default=False): cv.boolean, + vol.Optional( + CONF_SIGNAL_REPETITIONS, default=DEFAULT_SIGNAL_REPETITIONS + ): vol.Coerce(int), + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -38,9 +47,11 @@ def setup_platform(hass, config, add_entities, discovery_info=None): def cover_update(event): """Handle cover updates from the RFXtrx gateway.""" - if not isinstance(event.device, rfxtrxmod.LightingDevice) or \ - event.device.known_to_be_dimmable or \ - not event.device.known_to_be_rollershutter: + if ( + not isinstance(event.device, rfxtrxmod.LightingDevice) + or event.device.known_to_be_dimmable + or not event.device.known_to_be_rollershutter + ): return new_device = rfxtrx.get_new_device(event, config, RfxtrxCover) diff --git a/homeassistant/components/cover/rpi_gpio.py b/homeassistant/components/cover/rpi_gpio.py index 828f5e8e0..ce23a736b 100644 --- a/homeassistant/components/cover/rpi_gpio.py +++ b/homeassistant/components/cover/rpi_gpio.py @@ -19,39 +19,42 @@ import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -CONF_COVERS = 'covers' -CONF_RELAY_PIN = 'relay_pin' -CONF_RELAY_TIME = 'relay_time' -CONF_STATE_PIN = 'state_pin' -CONF_STATE_PULL_MODE = 'state_pull_mode' -CONF_INVERT_STATE = 'invert_state' -CONF_INVERT_RELAY = 'invert_relay' +CONF_COVERS = "covers" +CONF_RELAY_PIN = "relay_pin" +CONF_RELAY_TIME = "relay_time" +CONF_STATE_PIN = "state_pin" +CONF_STATE_PULL_MODE = "state_pull_mode" +CONF_INVERT_STATE = "invert_state" +CONF_INVERT_RELAY = "invert_relay" DEFAULT_RELAY_TIME = .2 -DEFAULT_STATE_PULL_MODE = 'UP' +DEFAULT_STATE_PULL_MODE = "UP" DEFAULT_INVERT_STATE = False DEFAULT_INVERT_RELAY = False -DEPENDENCIES = ['rpi_gpio'] +DEPENDENCIES = ["rpi_gpio"] _COVERS_SCHEMA = vol.All( cv.ensure_list, [ - vol.Schema({ - CONF_NAME: cv.string, - CONF_RELAY_PIN: cv.positive_int, - CONF_STATE_PIN: cv.positive_int, - }) - ] + vol.Schema( + { + CONF_NAME: cv.string, + CONF_RELAY_PIN: cv.positive_int, + CONF_STATE_PIN: cv.positive_int, + } + ) + ], ) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COVERS): _COVERS_SCHEMA, - vol.Optional(CONF_STATE_PULL_MODE, default=DEFAULT_STATE_PULL_MODE): - cv.string, - vol.Optional(CONF_RELAY_TIME, default=DEFAULT_RELAY_TIME): cv.positive_int, - vol.Optional(CONF_INVERT_STATE, default=DEFAULT_INVERT_STATE): cv.boolean, - vol.Optional(CONF_INVERT_RELAY, default=DEFAULT_INVERT_RELAY): cv.boolean, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_COVERS): _COVERS_SCHEMA, + vol.Optional(CONF_STATE_PULL_MODE, default=DEFAULT_STATE_PULL_MODE): cv.string, + vol.Optional(CONF_RELAY_TIME, default=DEFAULT_RELAY_TIME): cv.positive_int, + vol.Optional(CONF_INVERT_STATE, default=DEFAULT_INVERT_STATE): cv.boolean, + vol.Optional(CONF_INVERT_RELAY, default=DEFAULT_INVERT_RELAY): cv.boolean, + } +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -64,17 +67,33 @@ def setup_platform(hass, config, add_entities, discovery_info=None): covers_conf = config.get(CONF_COVERS) for cover in covers_conf: - covers.append(RPiGPIOCover( - cover[CONF_NAME], cover[CONF_RELAY_PIN], cover[CONF_STATE_PIN], - state_pull_mode, relay_time, invert_state, invert_relay)) + covers.append( + RPiGPIOCover( + cover[CONF_NAME], + cover[CONF_RELAY_PIN], + cover[CONF_STATE_PIN], + state_pull_mode, + relay_time, + invert_state, + invert_relay, + ) + ) add_entities(covers) class RPiGPIOCover(CoverDevice): """Representation of a Raspberry GPIO cover.""" - def __init__(self, name, relay_pin, state_pin, state_pull_mode, - relay_time, invert_state, invert_relay): + def __init__( + self, + name, + relay_pin, + state_pin, + state_pull_mode, + relay_time, + invert_state, + invert_relay, + ): """Initialize the cover.""" self._name = name self._state = False diff --git a/homeassistant/components/cover/ryobi_gdo.py b/homeassistant/components/cover/ryobi_gdo.py index fec91f843..3117546df 100644 --- a/homeassistant/components/cover/ryobi_gdo.py +++ b/homeassistant/components/cover/ryobi_gdo.py @@ -10,28 +10,39 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.cover import ( - CoverDevice, PLATFORM_SCHEMA, SUPPORT_OPEN, SUPPORT_CLOSE) + CoverDevice, + PLATFORM_SCHEMA, + SUPPORT_OPEN, + SUPPORT_CLOSE, +) from homeassistant.const import ( - CONF_USERNAME, CONF_PASSWORD, STATE_UNKNOWN, STATE_CLOSED) + CONF_USERNAME, + CONF_PASSWORD, + STATE_UNKNOWN, + STATE_CLOSED, +) -REQUIREMENTS = ['py_ryobi_gdo==0.0.10'] +REQUIREMENTS = ["py_ryobi_gdo==0.0.10"] _LOGGER = logging.getLogger(__name__) -CONF_DEVICE_ID = 'device_id' +CONF_DEVICE_ID = "device_id" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]), - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_DEVICE_ID): vol.All(cv.ensure_list, [cv.string]), + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + } +) -SUPPORTED_FEATURES = (SUPPORT_OPEN | SUPPORT_CLOSE) +SUPPORTED_FEATURES = SUPPORT_OPEN | SUPPORT_CLOSE def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Ryobi covers.""" from py_ryobi_gdo import RyobiGDO as ryobi_door + covers = [] username = config.get(CONF_USERNAME) @@ -61,7 +72,7 @@ class RyobiCover(CoverDevice): def __init__(self, hass, ryobi_door): """Initialize the cover.""" self.ryobi_door = ryobi_door - self._name = 'ryobi_gdo_{}'.format(ryobi_door.get_device_id()) + self._name = "ryobi_gdo_{}".format(ryobi_door.get_device_id()) self._door_state = None @property @@ -79,7 +90,7 @@ class RyobiCover(CoverDevice): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return 'garage' + return "garage" @property def supported_features(self): diff --git a/homeassistant/components/cover/scsgate.py b/homeassistant/components/cover/scsgate.py index a6f09c723..bf5e23b91 100644 --- a/homeassistant/components/cover/scsgate.py +++ b/homeassistant/components/cover/scsgate.py @@ -9,17 +9,17 @@ import logging import voluptuous as vol from homeassistant.components import scsgate -from homeassistant.components.cover import (CoverDevice, PLATFORM_SCHEMA) -from homeassistant.const import (CONF_DEVICES, CONF_NAME) +from homeassistant.components.cover import CoverDevice, PLATFORM_SCHEMA +from homeassistant.const import CONF_DEVICES, CONF_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['scsgate'] +DEPENDENCIES = ["scsgate"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_DEVICES): vol.Schema({cv.slug: scsgate.SCSGATE_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_DEVICES): vol.Schema({cv.slug: scsgate.SCSGATE_SCHEMA})} +) def setup_platform(hass, config, add_entities, discovery_info=None): @@ -78,15 +78,13 @@ class SCSGateCover(CoverDevice): """Move the cover.""" from scsgate.tasks import RaiseRollerShutterTask - scsgate.SCSGATE.append_task( - RaiseRollerShutterTask(target=self._scs_id)) + scsgate.SCSGATE.append_task(RaiseRollerShutterTask(target=self._scs_id)) def close_cover(self, **kwargs): """Move the cover down.""" from scsgate.tasks import LowerRollerShutterTask - scsgate.SCSGATE.append_task( - LowerRollerShutterTask(target=self._scs_id)) + scsgate.SCSGATE.append_task(LowerRollerShutterTask(target=self._scs_id)) def stop_cover(self, **kwargs): """Stop the cover.""" @@ -96,5 +94,4 @@ class SCSGateCover(CoverDevice): def process_event(self, message): """Handle a SCSGate message related with this cover.""" - self._logger.debug("Cover %s, got message %s", - self._scs_id, message.toggled) + self._logger.debug("Cover %s, got message %s", self._scs_id, message.toggled) diff --git a/homeassistant/components/cover/tahoma.py b/homeassistant/components/cover/tahoma.py index baf32073c..2537cac19 100644 --- a/homeassistant/components/cover/tahoma.py +++ b/homeassistant/components/cover/tahoma.py @@ -9,26 +9,25 @@ import logging from homeassistant.util.dt import utcnow from homeassistant.components.cover import CoverDevice, ATTR_POSITION -from homeassistant.components.tahoma import ( - DOMAIN as TAHOMA_DOMAIN, TahomaDevice) +from homeassistant.components.tahoma import DOMAIN as TAHOMA_DOMAIN, TahomaDevice -DEPENDENCIES = ['tahoma'] +DEPENDENCIES = ["tahoma"] _LOGGER = logging.getLogger(__name__) -ATTR_MEM_POS = 'memorized_position' -ATTR_RSSI_LEVEL = 'rssi_level' -ATTR_LOCK_START_TS = 'lock_start_ts' -ATTR_LOCK_END_TS = 'lock_end_ts' -ATTR_LOCK_LEVEL = 'lock_level' -ATTR_LOCK_ORIG = 'lock_originator' +ATTR_MEM_POS = "memorized_position" +ATTR_RSSI_LEVEL = "rssi_level" +ATTR_LOCK_START_TS = "lock_start_ts" +ATTR_LOCK_END_TS = "lock_end_ts" +ATTR_LOCK_LEVEL = "lock_level" +ATTR_LOCK_ORIG = "lock_originator" def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Tahoma covers.""" - controller = hass.data[TAHOMA_DOMAIN]['controller'] + controller = hass.data[TAHOMA_DOMAIN]["controller"] devices = [] - for device in hass.data[TAHOMA_DOMAIN]['devices']['cover']: + for device in hass.data[TAHOMA_DOMAIN]["devices"]["cover"]: devices.append(TahomaCover(device, controller)) add_entities(devices, True) @@ -63,29 +62,25 @@ class TahomaCover(TahomaDevice, CoverDevice): self.controller.get_states([self.tahoma_device]) # For vertical covers - self._closure = self.tahoma_device.active_states.get( - 'core:ClosureState') + self._closure = self.tahoma_device.active_states.get("core:ClosureState") # For horizontal covers if self._closure is None: - self._closure = self.tahoma_device.active_states.get( - 'core:DeploymentState') + self._closure = self.tahoma_device.active_states.get("core:DeploymentState") # For all, if available - if 'core:PriorityLockTimerState' in self.tahoma_device.active_states: + if "core:PriorityLockTimerState" in self.tahoma_device.active_states: old_lock_timer = self._lock_timer - self._lock_timer = \ - self.tahoma_device.active_states['core:PriorityLockTimerState'] + self._lock_timer = self.tahoma_device.active_states[ + "core:PriorityLockTimerState" + ] # Derive timestamps from _lock_timer, only if not already set or # something has changed if self._lock_timer > 0: - _LOGGER.debug("Update %s, lock_timer: %d", self._name, - self._lock_timer) + _LOGGER.debug("Update %s, lock_timer: %d", self._name, self._lock_timer) if self._lock_start_ts is None: self._lock_start_ts = utcnow() - if self._lock_end_ts is None or \ - old_lock_timer != self._lock_timer: - self._lock_end_ts = utcnow() +\ - timedelta(seconds=self._lock_timer) + if self._lock_end_ts is None or old_lock_timer != self._lock_timer: + self._lock_end_ts = utcnow() + timedelta(seconds=self._lock_timer) else: self._lock_start_ts = None self._lock_end_ts = None @@ -95,20 +90,21 @@ class TahomaCover(TahomaDevice, CoverDevice): self._lock_end_ts = None self._lock_level = self.tahoma_device.active_states.get( - 'io:PriorityLockLevelState') + "io:PriorityLockLevelState" + ) self._lock_originator = self.tahoma_device.active_states.get( - 'io:PriorityLockOriginatorState') + "io:PriorityLockOriginatorState" + ) - self._rssi_level = self.tahoma_device.active_states.get( - 'core:RSSILevelState') + self._rssi_level = self.tahoma_device.active_states.get("core:RSSILevelState") # Define which icon to use if self._lock_timer > 0: - if self._lock_originator == 'wind': - self._icon = 'mdi:weather-windy' + if self._lock_originator == "wind": + self._icon = "mdi:weather-windy" else: - self._icon = 'mdi:lock-alert' + self._icon = "mdi:lock-alert" else: self._icon = None @@ -124,10 +120,10 @@ class TahomaCover(TahomaDevice, CoverDevice): self._closed = self._position == 0 else: self._position = None - if 'core:OpenClosedState' in self.tahoma_device.active_states: - self._closed = \ - self.tahoma_device.active_states['core:OpenClosedState']\ - == 'closed' + if "core:OpenClosedState" in self.tahoma_device.active_states: + self._closed = ( + self.tahoma_device.active_states["core:OpenClosedState"] == "closed" + ) else: self._closed = False @@ -140,7 +136,7 @@ class TahomaCover(TahomaDevice, CoverDevice): def set_cover_position(self, **kwargs): """Move the cover to a specific position.""" - self.apply_action('setPosition', 100 - kwargs.get(ATTR_POSITION)) + self.apply_action("setPosition", 100 - kwargs.get(ATTR_POSITION)) @property def is_closed(self): @@ -150,8 +146,8 @@ class TahomaCover(TahomaDevice, CoverDevice): @property def device_class(self): """Return the class of the device.""" - if self.tahoma_device.type == 'io:WindowOpenerVeluxIOComponent': - return 'window' + if self.tahoma_device.type == "io:WindowOpenerVeluxIOComponent": + return "window" return None @property @@ -162,9 +158,10 @@ class TahomaCover(TahomaDevice, CoverDevice): if super_attr is not None: attr.update(super_attr) - if 'core:Memorized1PositionState' in self.tahoma_device.active_states: + if "core:Memorized1PositionState" in self.tahoma_device.active_states: attr[ATTR_MEM_POS] = self.tahoma_device.active_states[ - 'core:Memorized1PositionState'] + "core:Memorized1PositionState" + ] if self._rssi_level is not None: attr[ATTR_RSSI_LEVEL] = self._rssi_level if self._lock_start_ts is not None: @@ -184,35 +181,39 @@ class TahomaCover(TahomaDevice, CoverDevice): def open_cover(self, **kwargs): """Open the cover.""" - if self.tahoma_device.type == 'io:HorizontalAwningIOComponent': - self.apply_action('close') + if self.tahoma_device.type == "io:HorizontalAwningIOComponent": + self.apply_action("close") else: - self.apply_action('open') + self.apply_action("open") def close_cover(self, **kwargs): """Close the cover.""" - if self.tahoma_device.type == 'io:HorizontalAwningIOComponent': - self.apply_action('open') + if self.tahoma_device.type == "io:HorizontalAwningIOComponent": + self.apply_action("open") else: - self.apply_action('close') + self.apply_action("close") def stop_cover(self, **kwargs): """Stop the cover.""" - if self.tahoma_device.type == \ - 'io:RollerShutterWithLowSpeedManagementIOComponent': - self.apply_action('setPosition', 'secured') - elif self.tahoma_device.type in \ - ('rts:BlindRTSComponent', - 'io:ExteriorVenetianBlindIOComponent', - 'rts:VenetianBlindRTSComponent', - 'rts:DualCurtainRTSComponent', - 'rts:ExteriorVenetianBlindRTSComponent', - 'rts:BlindRTSComponent'): - self.apply_action('my') - elif self.tahoma_device.type in \ - ('io:HorizontalAwningIOComponent', - 'io:RollerShutterGenericIOComponent', - 'io:VerticalExteriorAwningIOComponent'): - self.apply_action('stop') + if ( + self.tahoma_device.type + == "io:RollerShutterWithLowSpeedManagementIOComponent" + ): + self.apply_action("setPosition", "secured") + elif self.tahoma_device.type in ( + "rts:BlindRTSComponent", + "io:ExteriorVenetianBlindIOComponent", + "rts:VenetianBlindRTSComponent", + "rts:DualCurtainRTSComponent", + "rts:ExteriorVenetianBlindRTSComponent", + "rts:BlindRTSComponent", + ): + self.apply_action("my") + elif self.tahoma_device.type in ( + "io:HorizontalAwningIOComponent", + "io:RollerShutterGenericIOComponent", + "io:VerticalExteriorAwningIOComponent", + ): + self.apply_action("stop") else: - self.apply_action('stopIdentify') + self.apply_action("stopIdentify") diff --git a/homeassistant/components/cover/tellstick.py b/homeassistant/components/cover/tellstick.py index 88608ac42..4af68a8d8 100644 --- a/homeassistant/components/cover/tellstick.py +++ b/homeassistant/components/cover/tellstick.py @@ -8,23 +8,30 @@ https://home-assistant.io/components/cover.tellstick/ from homeassistant.components.cover import CoverDevice from homeassistant.components.tellstick import ( - DEFAULT_SIGNAL_REPETITIONS, ATTR_DISCOVER_DEVICES, ATTR_DISCOVER_CONFIG, - DATA_TELLSTICK, TellstickDevice) + DEFAULT_SIGNAL_REPETITIONS, + ATTR_DISCOVER_DEVICES, + ATTR_DISCOVER_CONFIG, + DATA_TELLSTICK, + TellstickDevice, +) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Tellstick covers.""" - if (discovery_info is None or - discovery_info[ATTR_DISCOVER_DEVICES] is None): + if discovery_info is None or discovery_info[ATTR_DISCOVER_DEVICES] is None: return signal_repetitions = discovery_info.get( - ATTR_DISCOVER_CONFIG, DEFAULT_SIGNAL_REPETITIONS) + ATTR_DISCOVER_CONFIG, DEFAULT_SIGNAL_REPETITIONS + ) - add_entities([TellstickCover(hass.data[DATA_TELLSTICK][tellcore_id], - signal_repetitions) - for tellcore_id in discovery_info[ATTR_DISCOVER_DEVICES]], - True) + add_entities( + [ + TellstickCover(hass.data[DATA_TELLSTICK][tellcore_id], signal_repetitions) + for tellcore_id in discovery_info[ATTR_DISCOVER_DEVICES] + ], + True, + ) class TellstickCover(TellstickDevice, CoverDevice): diff --git a/homeassistant/components/cover/template.py b/homeassistant/components/cover/template.py index e02cdc323..c4c1a21bd 100644 --- a/homeassistant/components/cover/template.py +++ b/homeassistant/components/cover/template.py @@ -10,16 +10,32 @@ import voluptuous as vol from homeassistant.core import callback from homeassistant.components.cover import ( - ENTITY_ID_FORMAT, CoverDevice, PLATFORM_SCHEMA, - SUPPORT_OPEN_TILT, SUPPORT_CLOSE_TILT, SUPPORT_STOP_TILT, - SUPPORT_SET_TILT_POSITION, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP, - SUPPORT_SET_POSITION, ATTR_POSITION, ATTR_TILT_POSITION) + ENTITY_ID_FORMAT, + CoverDevice, + PLATFORM_SCHEMA, + SUPPORT_OPEN_TILT, + SUPPORT_CLOSE_TILT, + SUPPORT_STOP_TILT, + SUPPORT_SET_TILT_POSITION, + SUPPORT_OPEN, + SUPPORT_CLOSE, + SUPPORT_STOP, + SUPPORT_SET_POSITION, + ATTR_POSITION, + ATTR_TILT_POSITION, +) from homeassistant.const import ( - CONF_FRIENDLY_NAME, CONF_ENTITY_ID, - EVENT_HOMEASSISTANT_START, MATCH_ALL, - CONF_VALUE_TEMPLATE, CONF_ICON_TEMPLATE, - CONF_ENTITY_PICTURE_TEMPLATE, CONF_OPTIMISTIC, - STATE_OPEN, STATE_CLOSED) + CONF_FRIENDLY_NAME, + CONF_ENTITY_ID, + EVENT_HOMEASSISTANT_START, + MATCH_ALL, + CONF_VALUE_TEMPLATE, + CONF_ICON_TEMPLATE, + CONF_ENTITY_PICTURE_TEMPLATE, + CONF_OPTIMISTIC, + STATE_OPEN, + STATE_CLOSED, +) from homeassistant.exceptions import TemplateError import homeassistant.helpers.config_validation as cv from homeassistant.helpers.entity import async_generate_entity_id @@ -27,52 +43,59 @@ from homeassistant.helpers.event import async_track_state_change from homeassistant.helpers.script import Script _LOGGER = logging.getLogger(__name__) -_VALID_STATES = [STATE_OPEN, STATE_CLOSED, 'true', 'false'] +_VALID_STATES = [STATE_OPEN, STATE_CLOSED, "true", "false"] -CONF_COVERS = 'covers' +CONF_COVERS = "covers" -CONF_POSITION_TEMPLATE = 'position_template' -CONF_TILT_TEMPLATE = 'tilt_template' -OPEN_ACTION = 'open_cover' -CLOSE_ACTION = 'close_cover' -STOP_ACTION = 'stop_cover' -POSITION_ACTION = 'set_cover_position' -TILT_ACTION = 'set_cover_tilt_position' -CONF_TILT_OPTIMISTIC = 'tilt_optimistic' +CONF_POSITION_TEMPLATE = "position_template" +CONF_TILT_TEMPLATE = "tilt_template" +OPEN_ACTION = "open_cover" +CLOSE_ACTION = "close_cover" +STOP_ACTION = "stop_cover" +POSITION_ACTION = "set_cover_position" +TILT_ACTION = "set_cover_tilt_position" +CONF_TILT_OPTIMISTIC = "tilt_optimistic" -CONF_VALUE_OR_POSITION_TEMPLATE = 'value_or_position' -CONF_OPEN_OR_CLOSE = 'open_or_close' +CONF_VALUE_OR_POSITION_TEMPLATE = "value_or_position" +CONF_OPEN_OR_CLOSE = "open_or_close" -TILT_FEATURES = (SUPPORT_OPEN_TILT | SUPPORT_CLOSE_TILT | SUPPORT_STOP_TILT | - SUPPORT_SET_TILT_POSITION) +TILT_FEATURES = ( + SUPPORT_OPEN_TILT + | SUPPORT_CLOSE_TILT + | SUPPORT_STOP_TILT + | SUPPORT_SET_TILT_POSITION +) -COVER_SCHEMA = vol.Schema({ - vol.Inclusive(OPEN_ACTION, CONF_OPEN_OR_CLOSE): cv.SCRIPT_SCHEMA, - vol.Inclusive(CLOSE_ACTION, CONF_OPEN_OR_CLOSE): cv.SCRIPT_SCHEMA, - vol.Optional(STOP_ACTION): cv.SCRIPT_SCHEMA, - vol.Exclusive(CONF_POSITION_TEMPLATE, - CONF_VALUE_OR_POSITION_TEMPLATE): cv.template, - vol.Exclusive(CONF_VALUE_TEMPLATE, - CONF_VALUE_OR_POSITION_TEMPLATE): cv.template, - vol.Optional(CONF_POSITION_TEMPLATE): cv.template, - vol.Optional(CONF_TILT_TEMPLATE): cv.template, - vol.Optional(CONF_ICON_TEMPLATE): cv.template, - vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template, - vol.Optional(CONF_OPTIMISTIC): cv.boolean, - vol.Optional(CONF_TILT_OPTIMISTIC): cv.boolean, - vol.Optional(POSITION_ACTION): cv.SCRIPT_SCHEMA, - vol.Optional(TILT_ACTION): cv.SCRIPT_SCHEMA, - vol.Optional(CONF_FRIENDLY_NAME): cv.string, - vol.Optional(CONF_ENTITY_ID): cv.entity_ids -}) +COVER_SCHEMA = vol.Schema( + { + vol.Inclusive(OPEN_ACTION, CONF_OPEN_OR_CLOSE): cv.SCRIPT_SCHEMA, + vol.Inclusive(CLOSE_ACTION, CONF_OPEN_OR_CLOSE): cv.SCRIPT_SCHEMA, + vol.Optional(STOP_ACTION): cv.SCRIPT_SCHEMA, + vol.Exclusive( + CONF_POSITION_TEMPLATE, CONF_VALUE_OR_POSITION_TEMPLATE + ): cv.template, + vol.Exclusive( + CONF_VALUE_TEMPLATE, CONF_VALUE_OR_POSITION_TEMPLATE + ): cv.template, + vol.Optional(CONF_POSITION_TEMPLATE): cv.template, + vol.Optional(CONF_TILT_TEMPLATE): cv.template, + vol.Optional(CONF_ICON_TEMPLATE): cv.template, + vol.Optional(CONF_ENTITY_PICTURE_TEMPLATE): cv.template, + vol.Optional(CONF_OPTIMISTIC): cv.boolean, + vol.Optional(CONF_TILT_OPTIMISTIC): cv.boolean, + vol.Optional(POSITION_ACTION): cv.SCRIPT_SCHEMA, + vol.Optional(TILT_ACTION): cv.SCRIPT_SCHEMA, + vol.Optional(CONF_FRIENDLY_NAME): cv.string, + vol.Optional(CONF_ENTITY_ID): cv.entity_ids, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA})} +) -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 Template cover.""" covers = [] @@ -82,8 +105,7 @@ async def async_setup_platform(hass, config, async_add_entities, position_template = device_config.get(CONF_POSITION_TEMPLATE) tilt_template = device_config.get(CONF_TILT_TEMPLATE) icon_template = device_config.get(CONF_ICON_TEMPLATE) - entity_picture_template = device_config.get( - CONF_ENTITY_PICTURE_TEMPLATE) + entity_picture_template = device_config.get(CONF_ENTITY_PICTURE_TEMPLATE) open_action = device_config.get(OPEN_ACTION) close_action = device_config.get(CLOSE_ACTION) stop_action = device_config.get(STOP_ACTION) @@ -93,8 +115,9 @@ async def async_setup_platform(hass, config, async_add_entities, tilt_optimistic = device_config.get(CONF_TILT_OPTIMISTIC) if position_action is None and open_action is None: - _LOGGER.error('Must specify at least one of %s' or '%s', - OPEN_ACTION, POSITION_ACTION) + _LOGGER.error( + "Must specify at least one of %s" or "%s", OPEN_ACTION, POSITION_ACTION + ) continue template_entity_ids = set() if state_template is not None: @@ -130,11 +153,21 @@ async def async_setup_platform(hass, config, async_add_entities, covers.append( CoverTemplate( hass, - device, friendly_name, state_template, - position_template, tilt_template, icon_template, - entity_picture_template, open_action, close_action, - stop_action, position_action, tilt_action, - optimistic, tilt_optimistic, entity_ids + device, + friendly_name, + state_template, + position_template, + tilt_template, + icon_template, + entity_picture_template, + open_action, + close_action, + stop_action, + position_action, + tilt_action, + optimistic, + tilt_optimistic, + entity_ids, ) ) if not covers: @@ -148,15 +181,30 @@ async def async_setup_platform(hass, config, async_add_entities, class CoverTemplate(CoverDevice): """Representation of a Template cover.""" - def __init__(self, hass, device_id, friendly_name, state_template, - position_template, tilt_template, icon_template, - entity_picture_template, open_action, close_action, - stop_action, position_action, tilt_action, - optimistic, tilt_optimistic, entity_ids): + def __init__( + self, + hass, + device_id, + friendly_name, + state_template, + position_template, + tilt_template, + icon_template, + entity_picture_template, + open_action, + close_action, + stop_action, + position_action, + tilt_action, + optimistic, + tilt_optimistic, + entity_ids, + ): """Initialize the Template cover.""" self.hass = hass self.entity_id = async_generate_entity_id( - ENTITY_ID_FORMAT, device_id, hass=hass) + ENTITY_ID_FORMAT, device_id, hass=hass + ) self._name = friendly_name self._template = state_template self._position_template = position_template @@ -178,8 +226,7 @@ class CoverTemplate(CoverDevice): self._tilt_script = None if tilt_action is not None: self._tilt_script = Script(hass, tilt_action) - self._optimistic = (optimistic or - (not state_template and not position_template)) + self._optimistic = optimistic or (not state_template and not position_template) self._tilt_optimistic = tilt_optimistic or not tilt_template self._icon = None self._entity_picture = None @@ -200,6 +247,7 @@ class CoverTemplate(CoverDevice): async def async_added_to_hass(self): """Register callbacks.""" + @callback def template_cover_state_listener(entity, old_state, new_state): """Handle target device state changes.""" @@ -209,12 +257,14 @@ class CoverTemplate(CoverDevice): def template_cover_startup(event): """Update template on startup.""" async_track_state_change( - self.hass, self._entities, template_cover_state_listener) + self.hass, self._entities, template_cover_state_listener + ) self.async_schedule_update_ha_state(True) self.hass.bus.async_listen_once( - EVENT_HOMEASSISTANT_START, template_cover_startup) + EVENT_HOMEASSISTANT_START, template_cover_startup + ) @property def name(self): @@ -303,8 +353,7 @@ class CoverTemplate(CoverDevice): async def async_set_cover_position(self, **kwargs): """Set cover position.""" self._position = kwargs[ATTR_POSITION] - await self._position_script.async_run( - {"position": self._position}) + await self._position_script.async_run({"position": self._position}) if self._optimistic: self.async_schedule_update_ha_state() @@ -318,8 +367,7 @@ class CoverTemplate(CoverDevice): async def async_close_cover_tilt(self, **kwargs): """Tilt the cover closed.""" self._tilt_value = 0 - await self._tilt_script.async_run( - {"tilt": self._tilt_value}) + await self._tilt_script.async_run({"tilt": self._tilt_value}) if self._tilt_optimistic: self.async_schedule_update_ha_state() @@ -336,14 +384,16 @@ class CoverTemplate(CoverDevice): try: state = self._template.async_render().lower() if state in _VALID_STATES: - if state in ('true', STATE_OPEN): + if state in ("true", STATE_OPEN): self._position = 100 else: self._position = 0 else: _LOGGER.error( - 'Received invalid cover is_on state: %s. Expected: %s', - state, ', '.join(_VALID_STATES)) + "Received invalid cover is_on state: %s. Expected: %s", + state, + ", ".join(_VALID_STATES), + ) self._position = None except TemplateError as ex: _LOGGER.error(ex) @@ -353,9 +403,12 @@ class CoverTemplate(CoverDevice): state = float(self._position_template.async_render()) if state < 0 or state > 100: self._position = None - _LOGGER.error("Cover position value must be" - " between 0 and 100." - " Value was: %.2f", state) + _LOGGER.error( + "Cover position value must be" + " between 0 and 100." + " Value was: %.2f", + state, + ) else: self._position = state except TemplateError as ex: @@ -369,8 +422,10 @@ class CoverTemplate(CoverDevice): state = float(self._tilt_template.async_render()) if state < 0 or state > 100: self._tilt_value = None - _LOGGER.error("Tilt value must be between 0 and 100." - " Value was: %.2f", state) + _LOGGER.error( + "Tilt value must be between 0 and 100." " Value was: %.2f", + state, + ) else: self._tilt_value = state except TemplateError as ex: @@ -381,26 +436,33 @@ class CoverTemplate(CoverDevice): self._tilt_value = None for property_name, template in ( - ('_icon', self._icon_template), - ('_entity_picture', self._entity_picture_template)): + ("_icon", self._icon_template), + ("_entity_picture", self._entity_picture_template), + ): if template is None: continue try: setattr(self, property_name, template.async_render()) except TemplateError as ex: - friendly_property_name = property_name[1:].replace('_', ' ') + friendly_property_name = property_name[1:].replace("_", " ") if ex.args and ex.args[0].startswith( - "UndefinedError: 'None' has no attribute"): + "UndefinedError: 'None' has no attribute" + ): # Common during HA startup - so just a warning - _LOGGER.warning('Could not render %s template %s,' - ' the state is unknown.', - friendly_property_name, self._name) + _LOGGER.warning( + "Could not render %s template %s," " the state is unknown.", + friendly_property_name, + self._name, + ) return try: - setattr(self, property_name, - getattr(super(), property_name)) + setattr(self, property_name, getattr(super(), property_name)) except AttributeError: - _LOGGER.error('Could not render %s template %s: %s', - friendly_property_name, self._name, ex) + _LOGGER.error( + "Could not render %s template %s: %s", + friendly_property_name, + self._name, + ex, + ) diff --git a/homeassistant/components/cover/tuya.py b/homeassistant/components/cover/tuya.py index 6ab858160..67732f711 100644 --- a/homeassistant/components/cover/tuya.py +++ b/homeassistant/components/cover/tuya.py @@ -5,10 +5,15 @@ For more details about this platform, please refer to the documentation at https://home-assistant.io/components/cover.tuya/ """ from homeassistant.components.cover import ( - CoverDevice, ENTITY_ID_FORMAT, SUPPORT_OPEN, SUPPORT_CLOSE, SUPPORT_STOP) + CoverDevice, + ENTITY_ID_FORMAT, + SUPPORT_OPEN, + SUPPORT_CLOSE, + SUPPORT_STOP, +) from homeassistant.components.tuya import DATA_TUYA, TuyaDevice -DEPENDENCIES = ['tuya'] +DEPENDENCIES = ["tuya"] def setup_platform(hass, config, add_entities, discovery_info=None): @@ -16,7 +21,7 @@ def setup_platform(hass, config, add_entities, discovery_info=None): if discovery_info is None: return tuya = hass.data[DATA_TUYA] - dev_ids = discovery_info.get('dev_ids') + dev_ids = discovery_info.get("dev_ids") devices = [] for dev_id in dev_ids: device = tuya.get_device_by_id(dev_id) diff --git a/homeassistant/components/cover/velbus.py b/homeassistant/components/cover/velbus.py index a85017788..72acfddef 100644 --- a/homeassistant/components/cover/velbus.py +++ b/homeassistant/components/cover/velbus.py @@ -10,26 +10,32 @@ import time import voluptuous as vol from homeassistant.components.cover import ( - CoverDevice, PLATFORM_SCHEMA, SUPPORT_OPEN, SUPPORT_CLOSE, - SUPPORT_STOP) + CoverDevice, + PLATFORM_SCHEMA, + SUPPORT_OPEN, + SUPPORT_CLOSE, + SUPPORT_STOP, +) from homeassistant.components.velbus import DOMAIN -from homeassistant.const import (CONF_COVERS, CONF_NAME) +from homeassistant.const import CONF_COVERS, CONF_NAME import homeassistant.helpers.config_validation as cv _LOGGER = logging.getLogger(__name__) -COVER_SCHEMA = vol.Schema({ - vol.Required('module'): cv.positive_int, - vol.Required('open_channel'): cv.positive_int, - vol.Required('close_channel'): cv.positive_int, - vol.Required(CONF_NAME): cv.string -}) +COVER_SCHEMA = vol.Schema( + { + vol.Required("module"): cv.positive_int, + vol.Required("open_channel"): cv.positive_int, + vol.Required("close_channel"): cv.positive_int, + vol.Required(CONF_NAME): cv.string, + } +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA}), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Required(CONF_COVERS): vol.Schema({cv.slug: COVER_SCHEMA})} +) -DEPENDENCIES = ['velbus'] +DEPENDENCIES = ["velbus"] def setup_platform(hass, config, add_entities, discovery_info=None): @@ -43,9 +49,9 @@ def setup_platform(hass, config, add_entities, discovery_info=None): VelbusCover( velbus, device_config.get(CONF_NAME, device_name), - device_config.get('module'), - device_config.get('open_channel'), - device_config.get('close_channel') + device_config.get("module"), + device_config.get("open_channel"), + device_config.get("close_channel"), ) ) @@ -71,6 +77,7 @@ class VelbusCover(CoverDevice): async def async_added_to_hass(self): """Add listener for Velbus messages on bus.""" + def _init_velbus(): """Initialize Velbus on startup.""" self._velbus.subscribe(self._on_message) @@ -80,6 +87,7 @@ class VelbusCover(CoverDevice): def _on_message(self, message): import velbus + if isinstance(message, velbus.RelayStatusMessage): if message.address == self._module: if message.channel == self._close_channel: @@ -119,6 +127,7 @@ class VelbusCover(CoverDevice): def _relay_off(self, channel): import velbus + message = velbus.SwitchRelayOffMessage() message.set_defaults(self._module) message.relay_channels = [channel] @@ -126,6 +135,7 @@ class VelbusCover(CoverDevice): def _relay_on(self, channel): import velbus + message = velbus.SwitchRelayOnMessage() message.set_defaults(self._module) message.relay_channels = [channel] @@ -152,6 +162,7 @@ class VelbusCover(CoverDevice): def get_status(self): """Retrieve current status.""" import velbus + message = velbus.ModuleStatusRequestMessage() message.set_defaults(self._module) message.channels = [self._open_channel, self._close_channel] diff --git a/homeassistant/components/cover/vera.py b/homeassistant/components/cover/vera.py index 279e4a430..ce54f8de2 100644 --- a/homeassistant/components/cover/vera.py +++ b/homeassistant/components/cover/vera.py @@ -6,12 +6,10 @@ https://home-assistant.io/components/cover.vera/ """ import logging -from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT, \ - ATTR_POSITION -from homeassistant.components.vera import ( - VERA_CONTROLLER, VERA_DEVICES, VeraDevice) +from homeassistant.components.cover import CoverDevice, ENTITY_ID_FORMAT, ATTR_POSITION +from homeassistant.components.vera import VERA_CONTROLLER, VERA_DEVICES, VeraDevice -DEPENDENCIES = ['vera'] +DEPENDENCIES = ["vera"] _LOGGER = logging.getLogger(__name__) @@ -19,8 +17,12 @@ _LOGGER = logging.getLogger(__name__) def setup_platform(hass, config, add_entities, discovery_info=None): """Set up the Vera covers.""" add_entities( - [VeraCover(device, hass.data[VERA_CONTROLLER]) for - device in hass.data[VERA_DEVICES]['cover']], True) + [ + VeraCover(device, hass.data[VERA_CONTROLLER]) + for device in hass.data[VERA_DEVICES]["cover"] + ], + True, + ) class VeraCover(VeraDevice, CoverDevice): diff --git a/homeassistant/components/cover/wink.py b/homeassistant/components/cover/wink.py index 857283b9b..6b294c648 100644 --- a/homeassistant/components/cover/wink.py +++ b/homeassistant/components/cover/wink.py @@ -4,11 +4,10 @@ Support for Wink Covers. For more details about this platform, please refer to the documentation at https://home-assistant.io/components/cover.wink/ """ -from homeassistant.components.cover import CoverDevice, STATE_UNKNOWN, \ - ATTR_POSITION +from homeassistant.components.cover import CoverDevice, STATE_UNKNOWN, ATTR_POSITION from homeassistant.components.wink import WinkDevice, DOMAIN -DEPENDENCIES = ['wink'] +DEPENDENCIES = ["wink"] def setup_platform(hass, config, add_entities, discovery_info=None): @@ -17,15 +16,15 @@ def setup_platform(hass, config, add_entities, discovery_info=None): for shade in pywink.get_shades(): _id = shade.object_id() + shade.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: add_entities([WinkCoverDevice(shade, hass)]) for shade in pywink.get_shade_groups(): _id = shade.object_id() + shade.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: add_entities([WinkCoverDevice(shade, hass)]) for door in pywink.get_garage_doors(): _id = door.object_id() + door.name() - if _id not in hass.data[DOMAIN]['unique_ids']: + if _id not in hass.data[DOMAIN]["unique_ids"]: add_entities([WinkCoverDevice(door, hass)]) @@ -34,7 +33,7 @@ class WinkCoverDevice(WinkDevice, CoverDevice): async def async_added_to_hass(self): """Call when entity is added to hass.""" - self.hass.data[DOMAIN]['entities']['cover'].append(self) + self.hass.data[DOMAIN]["entities"]["cover"].append(self) def close_cover(self, **kwargs): """Close the cover.""" @@ -47,13 +46,13 @@ class WinkCoverDevice(WinkDevice, CoverDevice): def set_cover_position(self, **kwargs): """Move the cover shutter to a specific position.""" position = kwargs.get(ATTR_POSITION) - self.wink.set_state(position/100) + self.wink.set_state(position / 100) @property def current_cover_position(self): """Return the current position of cover shutter.""" if self.wink.state() is not None: - return int(self.wink.state()*100) + return int(self.wink.state() * 100) return STATE_UNKNOWN @property diff --git a/homeassistant/components/cover/xiaomi_aqara.py b/homeassistant/components/cover/xiaomi_aqara.py index 3ed0a70b1..c1b42b02d 100644 --- a/homeassistant/components/cover/xiaomi_aqara.py +++ b/homeassistant/components/cover/xiaomi_aqara.py @@ -2,25 +2,28 @@ import logging from homeassistant.components.cover import CoverDevice, ATTR_POSITION -from homeassistant.components.xiaomi_aqara import (PY_XIAOMI_GATEWAY, - XiaomiDevice) +from homeassistant.components.xiaomi_aqara import PY_XIAOMI_GATEWAY, XiaomiDevice _LOGGER = logging.getLogger(__name__) -ATTR_CURTAIN_LEVEL = 'curtain_level' +ATTR_CURTAIN_LEVEL = "curtain_level" def setup_platform(hass, config, add_entities, discovery_info=None): """Perform the setup for Xiaomi devices.""" devices = [] for (_, gateway) in hass.data[PY_XIAOMI_GATEWAY].gateways.items(): - for device in gateway.devices['cover']: - model = device['model'] - if model == 'curtain': - devices.append(XiaomiGenericCover(device, "Curtain", - {'status': 'status', - 'pos': 'curtain_level'}, - gateway)) + for device in gateway.devices["cover"]: + model = device["model"] + if model == "curtain": + devices.append( + XiaomiGenericCover( + device, + "Curtain", + {"status": "status", "pos": "curtain_level"}, + gateway, + ) + ) add_entities(devices) @@ -45,20 +48,20 @@ class XiaomiGenericCover(XiaomiDevice, CoverDevice): def close_cover(self, **kwargs): """Close the cover.""" - self._write_to_hub(self._sid, **{self._data_key['status']: 'close'}) + self._write_to_hub(self._sid, **{self._data_key["status"]: "close"}) def open_cover(self, **kwargs): """Open the cover.""" - self._write_to_hub(self._sid, **{self._data_key['status']: 'open'}) + self._write_to_hub(self._sid, **{self._data_key["status"]: "open"}) def stop_cover(self, **kwargs): """Stop the cover.""" - self._write_to_hub(self._sid, **{self._data_key['status']: 'stop'}) + self._write_to_hub(self._sid, **{self._data_key["status"]: "stop"}) def set_cover_position(self, **kwargs): """Move the cover to a specific position.""" position = kwargs.get(ATTR_POSITION) - self._write_to_hub(self._sid, **{self._data_key['pos']: str(position)}) + self._write_to_hub(self._sid, **{self._data_key["pos"]: str(position)}) def parse_data(self, data, raw_data): """Parse data sent by gateway.""" diff --git a/homeassistant/components/cover/zwave.py b/homeassistant/components/cover/zwave.py index 8c8c88ecb..c70634735 100644 --- a/homeassistant/components/cover/zwave.py +++ b/homeassistant/components/cover/zwave.py @@ -8,10 +8,16 @@ https://home-assistant.io/components/cover.zwave/ # pylint: disable=import-error import logging from homeassistant.components.cover import ( - DOMAIN, SUPPORT_OPEN, SUPPORT_CLOSE, ATTR_POSITION) + DOMAIN, + SUPPORT_OPEN, + SUPPORT_CLOSE, + ATTR_POSITION, +) from homeassistant.components.zwave import ZWaveDeviceEntity from homeassistant.components import zwave -from homeassistant.components.zwave import async_setup_platform # noqa pylint: disable=unused-import +from homeassistant.components.zwave import ( + async_setup_platform +) # noqa pylint: disable=unused-import from homeassistant.components.zwave import workaround from homeassistant.components.cover import CoverDevice @@ -23,14 +29,14 @@ SUPPORT_GARAGE = SUPPORT_OPEN | SUPPORT_CLOSE def get_device(hass, values, node_config, **kwargs): """Create Z-Wave entity device.""" invert_buttons = node_config.get(zwave.CONF_INVERT_OPENCLOSE_BUTTONS) - if (values.primary.command_class == - zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL - and values.primary.index == 0): + if ( + values.primary.command_class == zwave.const.COMMAND_CLASS_SWITCH_MULTILEVEL + and values.primary.index == 0 + ): return ZwaveRollershutter(hass, values, invert_buttons) if values.primary.command_class == zwave.const.COMMAND_CLASS_SWITCH_BINARY: return ZwaveGarageDoorSwitch(values) - if values.primary.command_class == \ - zwave.const.COMMAND_CLASS_BARRIER_OPERATOR: + if values.primary.command_class == zwave.const.COMMAND_CLASS_BARRIER_OPERATOR: return ZwaveGarageDoorBarrier(values) return None @@ -57,8 +63,12 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): # Position value self._current_position = self.values.primary.data - if self.values.open and self.values.close and \ - self._open_id is None and self._close_id is None: + if ( + self.values.open + and self.values.close + and self._open_id is None + and self._close_id is None + ): if self._invert_buttons: self._open_id = self.values.close.value_id self._close_id = self.values.open.value_id @@ -97,8 +107,7 @@ class ZwaveRollershutter(zwave.ZWaveDeviceEntity, CoverDevice): def set_cover_position(self, **kwargs): """Move the roller shutter to a specific position.""" - self.node.set_dimmer(self.values.primary.value_id, - kwargs.get(ATTR_POSITION)) + self.node.set_dimmer(self.values.primary.value_id, kwargs.get(ATTR_POSITION)) def stop_cover(self, **kwargs): """Stop the roller shutter.""" @@ -122,7 +131,7 @@ class ZwaveGarageDoorBase(zwave.ZWaveDeviceEntity, CoverDevice): @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" - return 'garage' + return "garage" @property def supported_features(self): diff --git a/homeassistant/components/daikin.py b/homeassistant/components/daikin.py index 8983ecf82..a302ad45d 100644 --- a/homeassistant/components/daikin.py +++ b/homeassistant/components/daikin.py @@ -13,68 +13,75 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.discovery import SERVICE_DAIKIN from homeassistant.const import ( - CONF_HOSTS, CONF_ICON, CONF_MONITORED_CONDITIONS, CONF_NAME, CONF_TYPE + CONF_HOSTS, + CONF_ICON, + CONF_MONITORED_CONDITIONS, + CONF_NAME, + CONF_TYPE, ) from homeassistant.helpers import discovery from homeassistant.helpers.discovery import load_platform from homeassistant.util import Throttle -REQUIREMENTS = ['pydaikin==0.4'] +REQUIREMENTS = ["pydaikin==0.4"] _LOGGER = logging.getLogger(__name__) -DOMAIN = 'daikin' -HTTP_RESOURCES = ['aircon/get_sensor_info', 'aircon/get_control_info'] +DOMAIN = "daikin" +HTTP_RESOURCES = ["aircon/get_sensor_info", "aircon/get_control_info"] -ATTR_TARGET_TEMPERATURE = 'target_temperature' -ATTR_INSIDE_TEMPERATURE = 'inside_temperature' -ATTR_OUTSIDE_TEMPERATURE = 'outside_temperature' +ATTR_TARGET_TEMPERATURE = "target_temperature" +ATTR_INSIDE_TEMPERATURE = "inside_temperature" +ATTR_OUTSIDE_TEMPERATURE = "outside_temperature" MIN_TIME_BETWEEN_UPDATES = timedelta(seconds=60) -COMPONENT_TYPES = ['climate', 'sensor'] +COMPONENT_TYPES = ["climate", "sensor"] -SENSOR_TYPE_TEMPERATURE = 'temperature' +SENSOR_TYPE_TEMPERATURE = "temperature" SENSOR_TYPES = { ATTR_INSIDE_TEMPERATURE: { - CONF_NAME: 'Inside Temperature', - CONF_ICON: 'mdi:thermometer', - CONF_TYPE: SENSOR_TYPE_TEMPERATURE + CONF_NAME: "Inside Temperature", + CONF_ICON: "mdi:thermometer", + CONF_TYPE: SENSOR_TYPE_TEMPERATURE, }, ATTR_OUTSIDE_TEMPERATURE: { - CONF_NAME: 'Outside Temperature', - CONF_ICON: 'mdi:thermometer', - CONF_TYPE: SENSOR_TYPE_TEMPERATURE - } - + CONF_NAME: "Outside Temperature", + CONF_ICON: "mdi:thermometer", + CONF_TYPE: SENSOR_TYPE_TEMPERATURE, + }, } -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional( - CONF_HOSTS, default=[] - ): vol.All(cv.ensure_list, [cv.string]), - vol.Optional( - CONF_MONITORED_CONDITIONS, - default=list(SENSOR_TYPES.keys()) - ): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]) - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_HOSTS, default=[]): vol.All( + cv.ensure_list, [cv.string] + ), + vol.Optional( + CONF_MONITORED_CONDITIONS, default=list(SENSOR_TYPES.keys()) + ): vol.All(cv.ensure_list, [vol.In(SENSOR_TYPES)]), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): """Establish connection with Daikin.""" + def discovery_dispatch(service, discovery_info): """Dispatcher for Daikin discovery events.""" - host = discovery_info.get('ip') + host = discovery_info.get("ip") if daikin_api_setup(hass, host) is None: return for component in COMPONENT_TYPES: - load_platform(hass, component, DOMAIN, discovery_info, - config) + load_platform(hass, component, DOMAIN, discovery_info, config) discovery.listen(hass, SERVICE_DAIKIN, discovery_dispatch) @@ -83,11 +90,10 @@ def setup(hass, config): continue discovery_info = { - 'ip': host, - CONF_MONITORED_CONDITIONS: - config[DOMAIN][CONF_MONITORED_CONDITIONS] + "ip": host, + CONF_MONITORED_CONDITIONS: config[DOMAIN][CONF_MONITORED_CONDITIONS], } - load_platform(hass, 'sensor', DOMAIN, discovery_info, config) + load_platform(hass, "sensor", DOMAIN, discovery_info, config) return True @@ -108,7 +114,7 @@ def daikin_api_setup(hass, host, name=None): return False if name is None: - name = device.values['name'] + name = device.values["name"] api = DaikinApi(device, name) @@ -129,10 +135,6 @@ class DaikinApi: """Pull the latest data from Daikin.""" try: for resource in HTTP_RESOURCES: - self.device.values.update( - self.device.get_resource(resource) - ) + self.device.values.update(self.device.get_resource(resource)) except timeout: - _LOGGER.warning( - "Connection failed for %s", self.ip_address - ) + _LOGGER.warning("Connection failed for %s", self.ip_address) diff --git a/homeassistant/components/datadog.py b/homeassistant/components/datadog.py index 58503d718..70f9a656a 100644 --- a/homeassistant/components/datadog.py +++ b/homeassistant/components/datadog.py @@ -9,31 +9,42 @@ import logging import voluptuous as vol from homeassistant.const import ( - CONF_HOST, CONF_PORT, CONF_PREFIX, EVENT_LOGBOOK_ENTRY, - EVENT_STATE_CHANGED, STATE_UNKNOWN) + CONF_HOST, + CONF_PORT, + CONF_PREFIX, + EVENT_LOGBOOK_ENTRY, + EVENT_STATE_CHANGED, + STATE_UNKNOWN, +) from homeassistant.helpers import state as state_helper import homeassistant.helpers.config_validation as cv -REQUIREMENTS = ['datadog==0.15.0'] +REQUIREMENTS = ["datadog==0.15.0"] _LOGGER = logging.getLogger(__name__) -CONF_RATE = 'rate' -DEFAULT_HOST = 'localhost' +CONF_RATE = "rate" +DEFAULT_HOST = "localhost" DEFAULT_PORT = 8125 -DEFAULT_PREFIX = 'hass' +DEFAULT_PREFIX = "hass" DEFAULT_RATE = 1 -DOMAIN = 'datadog' +DOMAIN = "datadog" -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string, - vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, - vol.Optional(CONF_PREFIX, default=DEFAULT_PREFIX): cv.string, - vol.Optional(CONF_RATE, default=DEFAULT_RATE): - vol.All(vol.Coerce(int), vol.Range(min=1)), - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Required(CONF_HOST, default=DEFAULT_HOST): cv.string, + vol.Optional(CONF_PORT, default=DEFAULT_PORT): cv.port, + vol.Optional(CONF_PREFIX, default=DEFAULT_PREFIX): cv.string, + vol.Optional(CONF_RATE, default=DEFAULT_RATE): vol.All( + vol.Coerce(int), vol.Range(min=1) + ), + } + ) + }, + extra=vol.ALLOW_EXTRA, +) def setup(hass, config): @@ -50,28 +61,28 @@ def setup(hass, config): def logbook_entry_listener(event): """Listen for logbook entries and send them as events.""" - name = event.data.get('name') - message = event.data.get('message') + name = event.data.get("name") + message = event.data.get("message") statsd.event( title="Home Assistant", text="%%% \n **{}** {} \n %%%".format(name, message), tags=[ - "entity:{}".format(event.data.get('entity_id')), - "domain:{}".format(event.data.get('domain')) - ] + "entity:{}".format(event.data.get("entity_id")), + "domain:{}".format(event.data.get("domain")), + ], ) - _LOGGER.debug('Sent event %s', event.data.get('entity_id')) + _LOGGER.debug("Sent event %s", event.data.get("entity_id")) def state_changed_listener(event): """Listen for new messages on the bus and sends them to Datadog.""" - state = event.data.get('new_state') + state = event.data.get("new_state") if state is None or state.state == STATE_UNKNOWN: return - if state.attributes.get('hidden') is True: + if state.attributes.get("hidden") is True: return states = dict(state.attributes) @@ -80,23 +91,20 @@ def setup(hass, config): for key, value in states.items(): if isinstance(value, (float, int)): - attribute = "{}.{}".format(metric, key.replace(' ', '_')) - statsd.gauge( - attribute, value, sample_rate=sample_rate, tags=tags) + attribute = "{}.{}".format(metric, key.replace(" ", "_")) + statsd.gauge(attribute, value, sample_rate=sample_rate, tags=tags) - _LOGGER.debug( - "Sent metric %s: %s (tags: %s)", attribute, value, tags) + _LOGGER.debug("Sent metric %s: %s (tags: %s)", attribute, value, tags) try: value = state_helper.state_as_number(state) except ValueError: - _LOGGER.debug( - "Error sending %s: %s (tags: %s)", metric, state.state, tags) + _LOGGER.debug("Error sending %s: %s (tags: %s)", metric, state.state, tags) return statsd.gauge(metric, value, sample_rate=sample_rate, tags=tags) - _LOGGER.debug('Sent metric %s: %s (tags: %s)', metric, value, tags) + _LOGGER.debug("Sent metric %s: %s (tags: %s)", metric, value, tags) hass.bus.listen(EVENT_LOGBOOK_ENTRY, logbook_entry_listener) hass.bus.listen(EVENT_STATE_CHANGED, state_changed_listener) diff --git a/homeassistant/components/deconz/__init__.py b/homeassistant/components/deconz/__init__.py index 6ed0a6e2c..6fa03abe5 100644 --- a/homeassistant/components/deconz/__init__.py +++ b/homeassistant/components/deconz/__init__.py @@ -8,45 +8,65 @@ import voluptuous as vol from homeassistant import config_entries from homeassistant.const import ( - CONF_API_KEY, CONF_EVENT, CONF_HOST, - CONF_ID, CONF_PORT, EVENT_HOMEASSISTANT_STOP) + CONF_API_KEY, + CONF_EVENT, + CONF_HOST, + CONF_ID, + CONF_PORT, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.core import EventOrigin, callback from homeassistant.helpers import aiohttp_client, config_validation as cv from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC from homeassistant.helpers.dispatcher import ( - async_dispatcher_connect, async_dispatcher_send) + async_dispatcher_connect, + async_dispatcher_send, +) from homeassistant.util import slugify from homeassistant.util.json import load_json # Loading the config flow file will register the flow from .config_flow import configured_hosts from .const import ( - CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DATA_DECONZ_EVENT, - DATA_DECONZ_ID, DATA_DECONZ_UNSUB, DOMAIN, _LOGGER) + CONF_ALLOW_CLIP_SENSOR, + CONFIG_FILE, + DATA_DECONZ_EVENT, + DATA_DECONZ_ID, + DATA_DECONZ_UNSUB, + DOMAIN, + _LOGGER, +) -REQUIREMENTS = ['pydeconz==47'] +REQUIREMENTS = ["pydeconz==47"] -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_API_KEY): cv.string, - vol.Optional(CONF_HOST): cv.string, - vol.Optional(CONF_PORT, default=80): cv.port, - }) -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_API_KEY): cv.string, + vol.Optional(CONF_HOST): cv.string, + vol.Optional(CONF_PORT, default=80): cv.port, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) -SERVICE_DECONZ = 'configure' +SERVICE_DECONZ = "configure" -SERVICE_FIELD = 'field' -SERVICE_ENTITY = 'entity' -SERVICE_DATA = 'data' +SERVICE_FIELD = "field" +SERVICE_ENTITY = "entity" +SERVICE_DATA = "data" -SERVICE_SCHEMA = vol.Schema({ - vol.Exclusive(SERVICE_FIELD, 'deconz_id'): cv.string, - vol.Exclusive(SERVICE_ENTITY, 'deconz_id'): cv.entity_id, - vol.Required(SERVICE_DATA): dict, -}) +SERVICE_SCHEMA = vol.Schema( + { + vol.Exclusive(SERVICE_FIELD, "deconz_id"): cv.string, + vol.Exclusive(SERVICE_ENTITY, "deconz_id"): cv.entity_id, + vol.Required(SERVICE_DATA): dict, + } +) -SERVICE_DEVICE_REFRESH = 'device_refresh' +SERVICE_DEVICE_REFRESH = "device_refresh" async def async_setup(hass, config): @@ -56,18 +76,19 @@ async def async_setup(hass, config): """ if DOMAIN in config: deconz_config = None - config_file = await hass.async_add_job( - load_json, hass.config.path(CONFIG_FILE)) + config_file = await hass.async_add_job(load_json, hass.config.path(CONFIG_FILE)) if config_file: deconz_config = config_file elif CONF_HOST in config[DOMAIN]: deconz_config = config[DOMAIN] if deconz_config and not configured_hosts(hass): - hass.async_add_job(hass.config_entries.flow.async_init( - DOMAIN, - context={'source': config_entries.SOURCE_IMPORT}, - data=deconz_config - )) + hass.async_add_job( + hass.config_entries.flow.async_init( + DOMAIN, + context={"source": config_entries.SOURCE_IMPORT}, + data=deconz_config, + ) + ) return True @@ -78,9 +99,9 @@ async def async_setup_entry(hass, config_entry): Start websocket for push notification of state changes from deCONZ. """ from pydeconz import DeconzSession + if DOMAIN in hass.data: - _LOGGER.error( - "Config entry failed since one deCONZ instance already exists") + _LOGGER.error("Config entry failed since one deCONZ instance already exists") return False @callback @@ -88,12 +109,15 @@ async def async_setup_entry(hass, config_entry): """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) + async_dispatcher_send(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) + 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: @@ -104,34 +128,41 @@ async def async_setup_entry(hass, config_entry): hass.data[DATA_DECONZ_EVENT] = [] hass.data[DATA_DECONZ_UNSUB] = [] - for component in ['binary_sensor', 'light', 'scene', 'sensor', 'switch']: - hass.async_create_task(hass.config_entries.async_forward_entry_setup( - config_entry, component)) + for component in ["binary_sensor", "light", "scene", "sensor", "switch"]: + hass.async_create_task( + hass.config_entries.async_forward_entry_setup(config_entry, component) + ) @callback def async_add_remote(sensors): """Set up remote from deCONZ.""" from pydeconz.sensor import SWITCH as DECONZ_REMOTE + allow_clip_sensor = config_entry.data.get(CONF_ALLOW_CLIP_SENSOR, True) for sensor in sensors: - if sensor.type in DECONZ_REMOTE and \ - not (not allow_clip_sensor and sensor.type.startswith('CLIP')): + if sensor.type in DECONZ_REMOTE and not ( + not allow_clip_sensor and sensor.type.startswith("CLIP") + ): hass.data[DATA_DECONZ_EVENT].append(DeconzEvent(hass, sensor)) + hass.data[DATA_DECONZ_UNSUB].append( - async_dispatcher_connect(hass, 'deconz_new_sensor', async_add_remote)) + async_dispatcher_connect(hass, "deconz_new_sensor", async_add_remote) + ) async_add_remote(deconz.sensors.values()) deconz.start() - device_registry = await \ - hass.helpers.device_registry.async_get_registry() + device_registry = await hass.helpers.device_registry.async_get_registry() device_registry.async_get_or_create( config_entry=config_entry.entry_id, connections={(CONNECTION_NETWORK_MAC, deconz.config.mac)}, identifiers={(DOMAIN, deconz.config.bridgeid)}, - manufacturer='Dresden Elektronik', model=deconz.config.modelid, - name=deconz.config.name, sw_version=deconz.config.swversion) + manufacturer="Dresden Elektronik", + model=deconz.config.modelid, + name=deconz.config.name, + sw_version=deconz.config.swversion, + ) async def async_configure(call): """Set attribute of device in deCONZ. @@ -160,13 +191,14 @@ async def async_setup_entry(hass, config_entry): field = entities.get(entity_id) if field is None: - _LOGGER.error('Could not find the entity %s', entity_id) + _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) + DOMAIN, SERVICE_DECONZ, async_configure, schema=SERVICE_SCHEMA + ) async def async_refresh_devices(call): """Refresh available devices from deCONZ.""" @@ -181,31 +213,42 @@ async def async_setup_entry(hass, config_entry): return async_add_device_callback( - 'group', [group - for group_id, group in deconz.groups.items() - if group_id not in groups] + "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] + "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] + "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] + "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) + hass.services.async_register(DOMAIN, SERVICE_DEVICE_REFRESH, async_refresh_devices) @callback def deconz_shutdown(event): @@ -228,9 +271,8 @@ async def async_unload_entry(hass, config_entry): hass.services.async_remove(DOMAIN, SERVICE_DECONZ) deconz.close() - for component in ['binary_sensor', 'light', 'scene', 'sensor', 'switch']: - await hass.config_entries.async_forward_entry_unload( - config_entry, component) + 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: @@ -258,7 +300,7 @@ class DeconzEvent: self._hass = hass self._device = device self._device.register_async_callback(self.async_update_callback) - self._event = 'deconz_{}'.format(CONF_EVENT) + self._event = "deconz_{}".format(CONF_EVENT) self._id = slugify(self._device.name) @callback @@ -270,6 +312,6 @@ class DeconzEvent: @callback def async_update_callback(self, reason): """Fire the event if reason is that state is updated.""" - if reason['state']: + if reason["state"]: data = {CONF_ID: self._id, CONF_EVENT: self._device.state} self._hass.bus.async_fire(self._event, data, EventOrigin.remote) diff --git a/homeassistant/components/deconz/config_flow.py b/homeassistant/components/deconz/config_flow.py index fb2eb5423..5573eaef9 100644 --- a/homeassistant/components/deconz/config_flow.py +++ b/homeassistant/components/deconz/config_flow.py @@ -8,18 +8,18 @@ from homeassistant.const import CONF_API_KEY, CONF_HOST, CONF_PORT from homeassistant.helpers import aiohttp_client from homeassistant.util.json import load_json -from .const import ( - CONF_ALLOW_DECONZ_GROUPS, CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DOMAIN) +from .const import CONF_ALLOW_DECONZ_GROUPS, CONF_ALLOW_CLIP_SENSOR, CONFIG_FILE, DOMAIN -CONF_BRIDGEID = 'bridgeid' +CONF_BRIDGEID = "bridgeid" @callback def configured_hosts(hass): """Return a set of the configured hosts.""" - return set(entry.data[CONF_HOST] for entry - in hass.config_entries.async_entries(DOMAIN)) + return set( + entry.data[CONF_HOST] for entry in hass.config_entries.async_entries(DOMAIN) + ) @config_entries.HANDLERS.register(DOMAIN) @@ -47,7 +47,7 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler): from pydeconz.utils import async_discovery if configured_hosts(self.hass): - return self.async_abort(reason='one_instance_only') + return self.async_abort(reason="one_instance_only") if user_input is not None: for bridge in self.bridges: @@ -66,35 +66,29 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler): for bridge in self.bridges: hosts.append(bridge[CONF_HOST]) return self.async_show_form( - step_id='init', - data_schema=vol.Schema({ - vol.Required(CONF_HOST): vol.In(hosts) - }) + step_id="init", + data_schema=vol.Schema({vol.Required(CONF_HOST): vol.In(hosts)}), ) - return self.async_abort( - reason='no_bridges' - ) + return self.async_abort(reason="no_bridges") async def async_step_link(self, user_input=None): """Attempt to link with the deCONZ bridge.""" from pydeconz.utils import async_get_api_key + errors = {} if user_input is not None: if configured_hosts(self.hass): - return self.async_abort(reason='one_instance_only') + return self.async_abort(reason="one_instance_only") session = aiohttp_client.async_get_clientsession(self.hass) api_key = await async_get_api_key(session, **self.deconz_config) if api_key: self.deconz_config[CONF_API_KEY] = api_key return await self.async_step_options() - errors['base'] = 'no_key' + errors["base"] = "no_key" - return self.async_show_form( - step_id='link', - errors=errors, - ) + return self.async_show_form(step_id="link", errors=errors) async def async_step_options(self, user_input=None): """Extra options for deCONZ. @@ -105,27 +99,32 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler): from pydeconz.utils import async_get_bridgeid if user_input is not None: - self.deconz_config[CONF_ALLOW_CLIP_SENSOR] = \ - user_input[CONF_ALLOW_CLIP_SENSOR] - self.deconz_config[CONF_ALLOW_DECONZ_GROUPS] = \ - user_input[CONF_ALLOW_DECONZ_GROUPS] + self.deconz_config[CONF_ALLOW_CLIP_SENSOR] = user_input[ + CONF_ALLOW_CLIP_SENSOR + ] + self.deconz_config[CONF_ALLOW_DECONZ_GROUPS] = user_input[ + CONF_ALLOW_DECONZ_GROUPS + ] if CONF_BRIDGEID not in self.deconz_config: session = aiohttp_client.async_get_clientsession(self.hass) self.deconz_config[CONF_BRIDGEID] = await async_get_bridgeid( - session, **self.deconz_config) + session, **self.deconz_config + ) return self.async_create_entry( - title='deCONZ-' + self.deconz_config[CONF_BRIDGEID], - data=self.deconz_config + title="deCONZ-" + self.deconz_config[CONF_BRIDGEID], + data=self.deconz_config, ) return self.async_show_form( - step_id='options', - data_schema=vol.Schema({ - vol.Optional(CONF_ALLOW_CLIP_SENSOR): bool, - vol.Optional(CONF_ALLOW_DECONZ_GROUPS): bool, - }), + step_id="options", + data_schema=vol.Schema( + { + vol.Optional(CONF_ALLOW_CLIP_SENSOR): bool, + vol.Optional(CONF_ALLOW_DECONZ_GROUPS): bool, + } + ), ) async def async_step_discovery(self, discovery_info): @@ -136,13 +135,16 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler): deconz_config = {} deconz_config[CONF_HOST] = discovery_info.get(CONF_HOST) deconz_config[CONF_PORT] = discovery_info.get(CONF_PORT) - deconz_config[CONF_BRIDGEID] = discovery_info.get('serial') + deconz_config[CONF_BRIDGEID] = discovery_info.get("serial") config_file = await self.hass.async_add_job( - load_json, self.hass.config.path(CONFIG_FILE)) - if config_file and \ - config_file[CONF_HOST] == deconz_config[CONF_HOST] and \ - CONF_API_KEY in config_file: + load_json, self.hass.config.path(CONFIG_FILE) + ) + if ( + config_file + and config_file[CONF_HOST] == deconz_config[CONF_HOST] + and CONF_API_KEY in config_file + ): deconz_config[CONF_API_KEY] = config_file[CONF_API_KEY] return await self.async_step_import(deconz_config) @@ -161,12 +163,11 @@ class DeconzFlowHandler(data_entry_flow.FlowHandler): will ask user to link the bridge. """ if configured_hosts(self.hass): - return self.async_abort(reason='one_instance_only') + return self.async_abort(reason="one_instance_only") self.deconz_config = import_config if CONF_API_KEY not in import_config: return await self.async_step_link() - user_input = {CONF_ALLOW_CLIP_SENSOR: True, - CONF_ALLOW_DECONZ_GROUPS: True} + user_input = {CONF_ALLOW_CLIP_SENSOR: True, CONF_ALLOW_DECONZ_GROUPS: True} return await self.async_step_options(user_input=user_input) diff --git a/homeassistant/components/deconz/const.py b/homeassistant/components/deconz/const.py index e629d57f2..9ddd6a341 100644 --- a/homeassistant/components/deconz/const.py +++ b/homeassistant/components/deconz/const.py @@ -1,20 +1,20 @@ """Constants for the deCONZ component.""" import logging -_LOGGER = logging.getLogger('homeassistant.components.deconz') +_LOGGER = logging.getLogger("homeassistant.components.deconz") -DOMAIN = 'deconz' -CONFIG_FILE = 'deconz.conf' -DATA_DECONZ_EVENT = 'deconz_events' -DATA_DECONZ_ID = 'deconz_entities' -DATA_DECONZ_UNSUB = 'deconz_dispatchers' -DECONZ_DOMAIN = 'deconz' +DOMAIN = "deconz" +CONFIG_FILE = "deconz.conf" +DATA_DECONZ_EVENT = "deconz_events" +DATA_DECONZ_ID = "deconz_entities" +DATA_DECONZ_UNSUB = "deconz_dispatchers" +DECONZ_DOMAIN = "deconz" -CONF_ALLOW_CLIP_SENSOR = 'allow_clip_sensor' -CONF_ALLOW_DECONZ_GROUPS = 'allow_deconz_groups' +CONF_ALLOW_CLIP_SENSOR = "allow_clip_sensor" +CONF_ALLOW_DECONZ_GROUPS = "allow_deconz_groups" -ATTR_DARK = 'dark' -ATTR_ON = 'on' +ATTR_DARK = "dark" +ATTR_ON = "on" POWER_PLUGS = ["On/Off plug-in unit", "Smart plug"] SIRENS = ["Warning device"] diff --git a/homeassistant/components/demo.py b/homeassistant/components/demo.py index c2c786614..6e1a3a2c0 100644 --- a/homeassistant/components/demo.py +++ b/homeassistant/components/demo.py @@ -11,27 +11,27 @@ from homeassistant import bootstrap import homeassistant.core as ha from homeassistant.const import ATTR_ENTITY_ID, CONF_PLATFORM -DEPENDENCIES = ['conversation', 'introduction', 'zone'] -DOMAIN = 'demo' +DEPENDENCIES = ["conversation", "introduction", "zone"] +DOMAIN = "demo" COMPONENTS_WITH_DEMO_PLATFORM = [ - 'alarm_control_panel', - 'binary_sensor', - 'calendar', - 'camera', - 'climate', - 'cover', - 'device_tracker', - 'fan', - 'image_processing', - 'light', - 'lock', - 'media_player', - 'notify', - 'sensor', - 'switch', - 'tts', - 'mailbox', + "alarm_control_panel", + "binary_sensor", + "calendar", + "camera", + "climate", + "cover", + "device_tracker", + "fan", + "image_processing", + "light", + "lock", + "media_player", + "notify", + "sensor", + "switch", + "tts", + "mailbox", ] @@ -45,8 +45,8 @@ def async_setup(hass, config): config.setdefault(ha.DOMAIN, {}) config.setdefault(DOMAIN, {}) - if config[DOMAIN].get('hide_demo_state') != 1: - hass.states.async_set('a.Demo_Mode', 'Enabled') + if config[DOMAIN].get("hide_demo_state") != 1: + hass.states.async_set("a.Demo_Mode", "Enabled") # Setup sun if not hass.config.latitude: @@ -55,51 +55,82 @@ def async_setup(hass, config): if not hass.config.longitude: hass.config.longitude = 117.22743 - tasks = [ - bootstrap.async_setup_component(hass, 'sun') - ] + tasks = [bootstrap.async_setup_component(hass, "sun")] # Set up demo platforms demo_config = config.copy() for component in COMPONENTS_WITH_DEMO_PLATFORM: - demo_config[component] = {CONF_PLATFORM: 'demo'} - tasks.append( - bootstrap.async_setup_component(hass, component, demo_config)) + demo_config[component] = {CONF_PLATFORM: "demo"} + tasks.append(bootstrap.async_setup_component(hass, component, demo_config)) # Set up input select - tasks.append(bootstrap.async_setup_component( - hass, 'input_select', - {'input_select': - {'living_room_preset': {'options': ['Visitors', - 'Visitors with kids', - 'Home Alone']}, - 'who_cooks': {'icon': 'mdi:panda', - 'initial': 'Anne Therese', - 'name': 'Cook today', - 'options': ['Paulus', 'Anne Therese']}}})) + tasks.append( + bootstrap.async_setup_component( + hass, + "input_select", + { + "input_select": { + "living_room_preset": { + "options": ["Visitors", "Visitors with kids", "Home Alone"] + }, + "who_cooks": { + "icon": "mdi:panda", + "initial": "Anne Therese", + "name": "Cook today", + "options": ["Paulus", "Anne Therese"], + }, + } + }, + ) + ) # Set up input boolean - tasks.append(bootstrap.async_setup_component( - hass, 'input_boolean', - {'input_boolean': {'notify': { - 'icon': 'mdi:car', - 'initial': False, - 'name': 'Notify Anne Therese is home'}}})) + tasks.append( + bootstrap.async_setup_component( + hass, + "input_boolean", + { + "input_boolean": { + "notify": { + "icon": "mdi:car", + "initial": False, + "name": "Notify Anne Therese is home", + } + } + }, + ) + ) # Set up input boolean - tasks.append(bootstrap.async_setup_component( - hass, 'input_number', - {'input_number': { - 'noise_allowance': {'icon': 'mdi:bell-ring', - 'min': 0, - 'max': 10, - 'name': 'Allowed Noise', - 'unit_of_measurement': 'dB'}}})) + tasks.append( + bootstrap.async_setup_component( + hass, + "input_number", + { + "input_number": { + "noise_allowance": { + "icon": "mdi:bell-ring", + "min": 0, + "max": 10, + "name": "Allowed Noise", + "unit_of_measurement": "dB", + } + } + }, + ) + ) # Set up weblink - tasks.append(bootstrap.async_setup_component( - hass, 'weblink', - {'weblink': {'entities': [{'name': 'Router', - 'url': 'http://192.168.1.1'}]}})) + tasks.append( + bootstrap.async_setup_component( + hass, + "weblink", + { + "weblink": { + "entities": [{"name": "Router", "url": "http://192.168.1.1"}] + } + }, + ) + ) results = yield from asyncio.gather(*tasks, loop=hass.loop) @@ -108,89 +139,162 @@ def async_setup(hass, config): # Set up example persistent notification persistent_notification.async_create( - 'This is an example of a persistent notification.', - title='Example Notification') + "This is an example of a persistent notification.", title="Example Notification" + ) # Set up room groups - lights = sorted(hass.states.async_entity_ids('light')) - switches = sorted(hass.states.async_entity_ids('switch')) - media_players = sorted(hass.states.async_entity_ids('media_player')) + lights = sorted(hass.states.async_entity_ids("light")) + switches = sorted(hass.states.async_entity_ids("switch")) + media_players = sorted(hass.states.async_entity_ids("media_player")) tasks2 = [] # Set up history graph - tasks2.append(bootstrap.async_setup_component( - hass, 'history_graph', - {'history_graph': {'switches': { - 'name': 'Recent Switches', - 'entities': switches, - 'hours_to_show': 1, - 'refresh': 60 - }}} - )) + tasks2.append( + bootstrap.async_setup_component( + hass, + "history_graph", + { + "history_graph": { + "switches": { + "name": "Recent Switches", + "entities": switches, + "hours_to_show": 1, + "refresh": 60, + } + } + }, + ) + ) # Set up scripts - tasks2.append(bootstrap.async_setup_component( - hass, 'script', - {'script': { - 'demo': { - 'alias': 'Toggle {}'.format(lights[0].split('.')[1]), - 'sequence': [{ - 'service': 'light.turn_off', - 'data': {ATTR_ENTITY_ID: lights[0]} - }, { - 'delay': {'seconds': 5} - }, { - 'service': 'light.turn_on', - 'data': {ATTR_ENTITY_ID: lights[0]} - }, { - 'delay': {'seconds': 5} - }, { - 'service': 'light.turn_off', - 'data': {ATTR_ENTITY_ID: lights[0]} - }] - }}})) + tasks2.append( + bootstrap.async_setup_component( + hass, + "script", + { + "script": { + "demo": { + "alias": "Toggle {}".format(lights[0].split(".")[1]), + "sequence": [ + { + "service": "light.turn_off", + "data": {ATTR_ENTITY_ID: lights[0]}, + }, + {"delay": {"seconds": 5}}, + { + "service": "light.turn_on", + "data": {ATTR_ENTITY_ID: lights[0]}, + }, + {"delay": {"seconds": 5}}, + { + "service": "light.turn_off", + "data": {ATTR_ENTITY_ID: lights[0]}, + }, + ], + } + } + }, + ) + ) # Set up scenes - tasks2.append(bootstrap.async_setup_component( - hass, 'scene', - {'scene': [ - {'name': 'Romantic lights', - 'entities': { - lights[0]: True, - lights[1]: {'state': 'on', 'xy_color': [0.33, 0.66], - 'brightness': 200}, - }}, - {'name': 'Switch on and off', - 'entities': { - switches[0]: True, - switches[1]: False, - }}, - ]})) + tasks2.append( + bootstrap.async_setup_component( + hass, + "scene", + { + "scene": [ + { + "name": "Romantic lights", + "entities": { + lights[0]: True, + lights[1]: { + "state": "on", + "xy_color": [0.33, 0.66], + "brightness": 200, + }, + }, + }, + { + "name": "Switch on and off", + "entities": {switches[0]: True, switches[1]: False}, + }, + ] + }, + ) + ) - tasks2.append(group.Group.async_create_group(hass, 'Living Room', [ - lights[1], switches[0], 'input_select.living_room_preset', - 'cover.living_room_window', media_players[1], - 'scene.romantic_lights'])) - tasks2.append(group.Group.async_create_group(hass, 'Bedroom', [ - lights[0], switches[1], media_players[0], - 'input_number.noise_allowance'])) - tasks2.append(group.Group.async_create_group(hass, 'Kitchen', [ - lights[2], 'cover.kitchen_window', 'lock.kitchen_door'])) - tasks2.append(group.Group.async_create_group(hass, 'Doors', [ - 'lock.front_door', 'lock.kitchen_door', - 'garage_door.right_garage_door', 'garage_door.left_garage_door'])) - tasks2.append(group.Group.async_create_group(hass, 'Automations', [ - 'input_select.who_cooks', 'input_boolean.notify', ])) - tasks2.append(group.Group.async_create_group(hass, 'People', [ - 'device_tracker.demo_anne_therese', 'device_tracker.demo_home_boy', - 'device_tracker.demo_paulus'])) - tasks2.append(group.Group.async_create_group(hass, 'Downstairs', [ - 'group.living_room', 'group.kitchen', - 'scene.romantic_lights', 'cover.kitchen_window', - 'cover.living_room_window', 'group.doors', - 'climate.ecobee', - ], view=True)) + tasks2.append( + group.Group.async_create_group( + hass, + "Living Room", + [ + lights[1], + switches[0], + "input_select.living_room_preset", + "cover.living_room_window", + media_players[1], + "scene.romantic_lights", + ], + ) + ) + tasks2.append( + group.Group.async_create_group( + hass, + "Bedroom", + [lights[0], switches[1], media_players[0], "input_number.noise_allowance"], + ) + ) + tasks2.append( + group.Group.async_create_group( + hass, "Kitchen", [lights[2], "cover.kitchen_window", "lock.kitchen_door"] + ) + ) + tasks2.append( + group.Group.async_create_group( + hass, + "Doors", + [ + "lock.front_door", + "lock.kitchen_door", + "garage_door.right_garage_door", + "garage_door.left_garage_door", + ], + ) + ) + tasks2.append( + group.Group.async_create_group( + hass, "Automations", ["input_select.who_cooks", "input_boolean.notify"] + ) + ) + tasks2.append( + group.Group.async_create_group( + hass, + "People", + [ + "device_tracker.demo_anne_therese", + "device_tracker.demo_home_boy", + "device_tracker.demo_paulus", + ], + ) + ) + tasks2.append( + group.Group.async_create_group( + hass, + "Downstairs", + [ + "group.living_room", + "group.kitchen", + "scene.romantic_lights", + "cover.kitchen_window", + "cover.living_room_window", + "group.doors", + "climate.ecobee", + ], + view=True, + ) + ) results = yield from asyncio.gather(*tasks2, loop=hass.loop) @@ -207,8 +311,8 @@ def async_setup(hass, config): # First time it is called, pretend it failed. if len(configurator_ids) == 1: configurator.notify_errors( - configurator_ids[0], - "Failed to register, please try again.") + configurator_ids[0], "Failed to register, please try again." + ) configurator_ids.append(0) else: @@ -217,12 +321,15 @@ def async_setup(hass, config): def setup_configurator(): """Set up a configurator.""" request_id = configurator.request_config( - "Philips Hue", hue_configuration_callback, - description=("Press the button on the bridge to register Philips " - "Hue with Home Assistant."), + "Philips Hue", + hue_configuration_callback, + description=( + "Press the button on the bridge to register Philips " + "Hue with Home Assistant." + ), description_image="/static/images/config_philips_hue.jpg", - fields=[{'id': 'username', 'name': 'Username'}], - submit_caption="I have pressed the button" + fields=[{"id": "username", "name": "Username"}], + submit_caption="I have pressed the button", ) configurator_ids.append(request_id) diff --git a/homeassistant/components/device_sun_light_trigger.py b/homeassistant/components/device_sun_light_trigger.py index 641ade730..4d4b4425a 100644 --- a/homeassistant/components/device_sun_light_trigger.py +++ b/homeassistant/components/device_sun_light_trigger.py @@ -14,33 +14,42 @@ from homeassistant.core import callback import homeassistant.util.dt as dt_util from homeassistant.const import STATE_HOME, STATE_NOT_HOME from homeassistant.helpers.event import ( - async_track_point_in_utc_time, async_track_state_change) + async_track_point_in_utc_time, + async_track_state_change, +) from homeassistant.helpers.sun import is_up, get_astral_event_next import homeassistant.helpers.config_validation as cv -DOMAIN = 'device_sun_light_trigger' -DEPENDENCIES = ['light', 'device_tracker', 'group'] +DOMAIN = "device_sun_light_trigger" +DEPENDENCIES = ["light", "device_tracker", "group"] -CONF_DEVICE_GROUP = 'device_group' -CONF_DISABLE_TURN_OFF = 'disable_turn_off' -CONF_LIGHT_GROUP = 'light_group' -CONF_LIGHT_PROFILE = 'light_profile' +CONF_DEVICE_GROUP = "device_group" +CONF_DISABLE_TURN_OFF = "disable_turn_off" +CONF_LIGHT_GROUP = "light_group" +CONF_LIGHT_PROFILE = "light_profile" DEFAULT_DISABLE_TURN_OFF = False -DEFAULT_LIGHT_PROFILE = 'relax' +DEFAULT_LIGHT_PROFILE = "relax" LIGHT_TRANSITION_TIME = timedelta(minutes=15) -CONFIG_SCHEMA = vol.Schema({ - DOMAIN: vol.Schema({ - vol.Optional(CONF_DEVICE_GROUP): cv.entity_id, - vol.Optional(CONF_DISABLE_TURN_OFF, default=DEFAULT_DISABLE_TURN_OFF): - cv.boolean, - vol.Optional(CONF_LIGHT_GROUP): cv.string, - vol.Optional(CONF_LIGHT_PROFILE, default=DEFAULT_LIGHT_PROFILE): - cv.string, - }), -}, extra=vol.ALLOW_EXTRA) +CONFIG_SCHEMA = vol.Schema( + { + DOMAIN: vol.Schema( + { + vol.Optional(CONF_DEVICE_GROUP): cv.entity_id, + vol.Optional( + CONF_DISABLE_TURN_OFF, default=DEFAULT_DISABLE_TURN_OFF + ): cv.boolean, + vol.Optional(CONF_LIGHT_GROUP): cv.string, + vol.Optional( + CONF_LIGHT_PROFILE, default=DEFAULT_LIGHT_PROFILE + ): cv.string, + } + ) + }, + extra=vol.ALLOW_EXTRA, +) @asyncio.coroutine @@ -54,10 +63,8 @@ def async_setup(hass, config): disable_turn_off = conf.get(CONF_DISABLE_TURN_OFF) light_group = conf.get(CONF_LIGHT_GROUP, light.ENTITY_ID_ALL_LIGHTS) light_profile = conf.get(CONF_LIGHT_PROFILE) - device_group = conf.get( - CONF_DEVICE_GROUP, device_tracker.ENTITY_ID_ALL_DEVICES) - device_entity_ids = group.get_entity_ids( - device_group, device_tracker.DOMAIN) + device_group = conf.get(CONF_DEVICE_GROUP, device_tracker.ENTITY_ID_ALL_DEVICES) + device_entity_ids = group.get_entity_ids(device_group, device_tracker.DOMAIN) if not device_entity_ids: logger.error("No devices found to track") @@ -77,7 +84,7 @@ def async_setup(hass, config): Async friendly. """ - next_setting = get_astral_event_next(hass, 'sunset') + next_setting = get_astral_event_next(hass, "sunset") if not next_setting: return None return next_setting - LIGHT_TRANSITION_TIME * len(light_ids) @@ -86,12 +93,13 @@ def async_setup(hass, config): """Turn on lights.""" if not device_tracker.is_on() or light.is_on(light_id): return - light.async_turn_on(light_id, - transition=LIGHT_TRANSITION_TIME.seconds, - profile=light_profile) + light.async_turn_on( + light_id, transition=LIGHT_TRANSITION_TIME.seconds, profile=light_profile + ) def async_turn_on_factory(light_id): """Generate turn on callbacks as factory.""" + @callback def async_turn_on_light(now): """Turn on specific light.""" @@ -114,11 +122,14 @@ def async_setup(hass, config): for index, light_id in enumerate(light_ids): async_track_point_in_utc_time( - hass, async_turn_on_factory(light_id), - start_point + index * LIGHT_TRANSITION_TIME) + hass, + async_turn_on_factory(light_id), + start_point + index * LIGHT_TRANSITION_TIME, + ) - async_track_point_in_utc_time(hass, schedule_light_turn_on, - get_astral_event_next(hass, 'sunrise')) + async_track_point_in_utc_time( + hass, schedule_light_turn_on, get_astral_event_next(hass, "sunrise") + ) # If the sun is already above horizon schedule the time-based pre-sun set # event. @@ -144,8 +155,7 @@ def async_setup(hass, config): # if someone would be home? # Check this by seeing if current time is later then the point # in time when we would start putting the lights on. - elif (start_point and - start_point < now < get_astral_event_next(hass, 'sunset')): + elif start_point and start_point < now < get_astral_event_next(hass, "sunset"): # Check for every light if it would be on if someone was home # when the fading in started and turn it on if so @@ -159,8 +169,12 @@ def async_setup(hass, config): break async_track_state_change( - hass, device_entity_ids, check_light_on_dev_state_change, - STATE_NOT_HOME, STATE_HOME) + hass, + device_entity_ids, + check_light_on_dev_state_change, + STATE_NOT_HOME, + STATE_HOME, + ) if disable_turn_off: return True @@ -171,12 +185,11 @@ def async_setup(hass, config): if not group.is_on(light_group): return - logger.info( - "Everyone has left but there are lights on. Turning them off") + logger.info("Everyone has left but there are lights on. Turning them off") light.async_turn_off(light_ids) async_track_state_change( - hass, device_group, turn_off_lights_when_all_leave, - STATE_HOME, STATE_NOT_HOME) + hass, device_group, turn_off_lights_when_all_leave, STATE_HOME, STATE_NOT_HOME + ) return True diff --git a/homeassistant/components/device_tracker/__init__.py b/homeassistant/components/device_tracker/__init__.py index 408672a97..a91b33720 100644 --- a/homeassistant/components/device_tracker/__init__.py +++ b/homeassistant/components/device_tracker/__init__.py @@ -31,85 +31,109 @@ from homeassistant.util.yaml import dump from homeassistant.helpers.event import async_track_utc_time_change from homeassistant.const import ( - ATTR_GPS_ACCURACY, ATTR_LATITUDE, ATTR_LONGITUDE, CONF_NAME, CONF_MAC, - DEVICE_DEFAULT_NAME, STATE_HOME, STATE_NOT_HOME, ATTR_ENTITY_ID, - CONF_ICON, ATTR_ICON, ATTR_NAME) + ATTR_GPS_ACCURACY, + ATTR_LATITUDE, + ATTR_LONGITUDE, + CONF_NAME, + CONF_MAC, + DEVICE_DEFAULT_NAME, + STATE_HOME, + STATE_NOT_HOME, + ATTR_ENTITY_ID, + CONF_ICON, + ATTR_ICON, + ATTR_NAME, +) _LOGGER = logging.getLogger(__name__) -DOMAIN = 'device_tracker' -DEPENDENCIES = ['zone', 'group'] +DOMAIN = "device_tracker" +DEPENDENCIES = ["zone", "group"] -GROUP_NAME_ALL_DEVICES = 'all devices' -ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format('all_devices') +GROUP_NAME_ALL_DEVICES = "all devices" +ENTITY_ID_ALL_DEVICES = group.ENTITY_ID_FORMAT.format("all_devices") -ENTITY_ID_FORMAT = DOMAIN + '.{}' +ENTITY_ID_FORMAT = DOMAIN + ".{}" -YAML_DEVICES = 'known_devices.yaml' +YAML_DEVICES = "known_devices.yaml" -CONF_TRACK_NEW = 'track_new_devices' +CONF_TRACK_NEW = "track_new_devices" DEFAULT_TRACK_NEW = True -CONF_NEW_DEVICE_DEFAULTS = 'new_device_defaults' +CONF_NEW_DEVICE_DEFAULTS = "new_device_defaults" -CONF_CONSIDER_HOME = 'consider_home' +CONF_CONSIDER_HOME = "consider_home" DEFAULT_CONSIDER_HOME = timedelta(seconds=180) -CONF_SCAN_INTERVAL = 'interval_seconds' +CONF_SCAN_INTERVAL = "interval_seconds" DEFAULT_SCAN_INTERVAL = timedelta(seconds=12) -CONF_AWAY_HIDE = 'hide_if_away' +CONF_AWAY_HIDE = "hide_if_away" DEFAULT_AWAY_HIDE = False -EVENT_NEW_DEVICE = 'device_tracker_new_device' +EVENT_NEW_DEVICE = "device_tracker_new_device" -SERVICE_SEE = 'see' +SERVICE_SEE = "see" -ATTR_ATTRIBUTES = 'attributes' -ATTR_BATTERY = 'battery' -ATTR_DEV_ID = 'dev_id' -ATTR_GPS = 'gps' -ATTR_HOST_NAME = 'host_name' -ATTR_LOCATION_NAME = 'location_name' -ATTR_MAC = 'mac' -ATTR_SOURCE_TYPE = 'source_type' -ATTR_CONSIDER_HOME = 'consider_home' +ATTR_ATTRIBUTES = "attributes" +ATTR_BATTERY = "battery" +ATTR_DEV_ID = "dev_id" +ATTR_GPS = "gps" +ATTR_HOST_NAME = "host_name" +ATTR_LOCATION_NAME = "location_name" +ATTR_MAC = "mac" +ATTR_SOURCE_TYPE = "source_type" +ATTR_CONSIDER_HOME = "consider_home" -SOURCE_TYPE_GPS = 'gps' -SOURCE_TYPE_ROUTER = 'router' -SOURCE_TYPE_BLUETOOTH = 'bluetooth' -SOURCE_TYPE_BLUETOOTH_LE = 'bluetooth_le' -SOURCE_TYPES = (SOURCE_TYPE_GPS, SOURCE_TYPE_ROUTER, - SOURCE_TYPE_BLUETOOTH, SOURCE_TYPE_BLUETOOTH_LE) +SOURCE_TYPE_GPS = "gps" +SOURCE_TYPE_ROUTER = "router" +SOURCE_TYPE_BLUETOOTH = "bluetooth" +SOURCE_TYPE_BLUETOOTH_LE = "bluetooth_le" +SOURCE_TYPES = ( + SOURCE_TYPE_GPS, + SOURCE_TYPE_ROUTER, + SOURCE_TYPE_BLUETOOTH, + SOURCE_TYPE_BLUETOOTH_LE, +) -NEW_DEVICE_DEFAULTS_SCHEMA = vol.Any(None, vol.Schema({ - vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean, - vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean, -})) -PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_SCAN_INTERVAL): cv.time_period, - vol.Optional(CONF_TRACK_NEW): cv.boolean, - vol.Optional(CONF_CONSIDER_HOME, - default=DEFAULT_CONSIDER_HOME): vol.All( - cv.time_period, cv.positive_timedelta), - vol.Optional(CONF_NEW_DEVICE_DEFAULTS, - default={}): NEW_DEVICE_DEFAULTS_SCHEMA -}) -SERVICE_SEE_PAYLOAD_SCHEMA = vol.Schema(vol.All( - cv.has_at_least_one_key(ATTR_MAC, ATTR_DEV_ID), { - ATTR_MAC: cv.string, - ATTR_DEV_ID: cv.string, - ATTR_HOST_NAME: cv.string, - ATTR_LOCATION_NAME: cv.string, - ATTR_GPS: cv.gps, - ATTR_GPS_ACCURACY: cv.positive_int, - ATTR_BATTERY: cv.positive_int, - ATTR_ATTRIBUTES: dict, - ATTR_SOURCE_TYPE: vol.In(SOURCE_TYPES), - ATTR_CONSIDER_HOME: cv.time_period, - # Temp workaround for iOS app introduced in 0.65 - vol.Optional('battery_status'): str, - vol.Optional('hostname'): str, - })) +NEW_DEVICE_DEFAULTS_SCHEMA = vol.Any( + None, + vol.Schema( + { + vol.Optional(CONF_TRACK_NEW, default=DEFAULT_TRACK_NEW): cv.boolean, + vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean, + } + ), +) +PLATFORM_SCHEMA = cv.PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_SCAN_INTERVAL): cv.time_period, + vol.Optional(CONF_TRACK_NEW): cv.boolean, + vol.Optional(CONF_CONSIDER_HOME, default=DEFAULT_CONSIDER_HOME): vol.All( + cv.time_period, cv.positive_timedelta + ), + vol.Optional(CONF_NEW_DEVICE_DEFAULTS, default={}): NEW_DEVICE_DEFAULTS_SCHEMA, + } +) +SERVICE_SEE_PAYLOAD_SCHEMA = vol.Schema( + vol.All( + cv.has_at_least_one_key(ATTR_MAC, ATTR_DEV_ID), + { + ATTR_MAC: cv.string, + ATTR_DEV_ID: cv.string, + ATTR_HOST_NAME: cv.string, + ATTR_LOCATION_NAME: cv.string, + ATTR_GPS: cv.gps, + ATTR_GPS_ACCURACY: cv.positive_int, + ATTR_BATTERY: cv.positive_int, + ATTR_ATTRIBUTES: dict, + ATTR_SOURCE_TYPE: vol.In(SOURCE_TYPES), + ATTR_CONSIDER_HOME: cv.time_period, + # Temp workaround for iOS app introduced in 0.65 + vol.Optional("battery_status"): str, + vol.Optional("hostname"): str, + }, + ) +) @bind_hass @@ -120,19 +144,31 @@ def is_on(hass: HomeAssistantType, entity_id: str = None): return hass.states.is_state(entity, STATE_HOME) -def see(hass: HomeAssistantType, mac: str = None, dev_id: str = None, - host_name: str = None, location_name: str = None, - gps: GPSType = None, gps_accuracy=None, - battery: int = None, attributes: dict = None): +def see( + hass: HomeAssistantType, + mac: str = None, + dev_id: str = None, + host_name: str = None, + location_name: str = None, + gps: GPSType = None, + gps_accuracy=None, + battery: int = None, + attributes: dict = None, +): """Call service to notify you see device.""" - data = {key: value for key, value in - ((ATTR_MAC, mac), - (ATTR_DEV_ID, dev_id), - (ATTR_HOST_NAME, host_name), - (ATTR_LOCATION_NAME, location_name), - (ATTR_GPS, gps), - (ATTR_GPS_ACCURACY, gps_accuracy), - (ATTR_BATTERY, battery)) if value is not None} + data = { + key: value + for key, value in ( + (ATTR_MAC, mac), + (ATTR_DEV_ID, dev_id), + (ATTR_HOST_NAME, host_name), + (ATTR_LOCATION_NAME, location_name), + (ATTR_GPS, gps), + (ATTR_GPS_ACCURACY, gps_accuracy), + (ATTR_BATTERY, battery), + ) + if value is not None + } if attributes: data[ATTR_ATTRIBUTES] = attributes hass.services.call(DOMAIN, SERVICE_SEE, data) @@ -153,14 +189,12 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): track_new = defaults.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW) devices = yield from async_load_config(yaml_path, hass, consider_home) - tracker = DeviceTracker( - hass, consider_home, track_new, defaults, devices) + tracker = DeviceTracker(hass, consider_home, track_new, defaults, devices) @asyncio.coroutine def async_setup_platform(p_type, p_config, disc_info=None): """Set up a device tracker platform.""" - platform = yield from async_prepare_setup_platform( - hass, config, DOMAIN, p_type) + platform = yield from async_prepare_setup_platform(hass, config, DOMAIN, p_type) if platform is None: return @@ -168,25 +202,29 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): try: scanner = None setup = None - if hasattr(platform, 'async_get_scanner'): + if hasattr(platform, "async_get_scanner"): scanner = yield from platform.async_get_scanner( - hass, {DOMAIN: p_config}) - elif hasattr(platform, 'get_scanner'): + hass, {DOMAIN: p_config} + ) + elif hasattr(platform, "get_scanner"): scanner = yield from hass.async_add_job( - platform.get_scanner, hass, {DOMAIN: p_config}) - elif hasattr(platform, 'async_setup_scanner'): + platform.get_scanner, hass, {DOMAIN: p_config} + ) + elif hasattr(platform, "async_setup_scanner"): setup = yield from platform.async_setup_scanner( - hass, p_config, tracker.async_see, disc_info) - elif hasattr(platform, 'setup_scanner'): + hass, p_config, tracker.async_see, disc_info + ) + elif hasattr(platform, "setup_scanner"): setup = yield from hass.async_add_job( - platform.setup_scanner, hass, p_config, tracker.see, - disc_info) + platform.setup_scanner, hass, p_config, tracker.see, disc_info + ) else: raise HomeAssistantError("Invalid device_tracker platform.") if scanner: async_setup_scanner_platform( - hass, p_config, scanner, tracker.async_see, p_type) + hass, p_config, scanner, tracker.async_see, p_type + ) return if not setup: @@ -196,8 +234,10 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): except Exception: # pylint: disable=broad-except _LOGGER.exception("Error setting up platform %s", p_type) - setup_tasks = [async_setup_platform(p_type, p_config) for p_type, p_config - in config_per_platform(config, DOMAIN)] + setup_tasks = [ + async_setup_platform(p_type, p_config) + for p_type, p_config in config_per_platform(config, DOMAIN) + ] if setup_tasks: yield from asyncio.wait(setup_tasks, loop=hass.loop) @@ -212,19 +252,21 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): # Clean up stale devices async_track_utc_time_change( - hass, tracker.async_update_stale, second=range(0, 60, 5)) + hass, tracker.async_update_stale, second=range(0, 60, 5) + ) @asyncio.coroutine def async_see_service(call): """Service to see a device.""" # Temp workaround for iOS, introduced in 0.65 data = dict(call.data) - data.pop('hostname', None) - data.pop('battery_status', None) + data.pop("hostname", None) + data.pop("battery_status", None) yield from tracker.async_see(**data) hass.services.async_register( - DOMAIN, SERVICE_SEE, async_see_service, SERVICE_SEE_PAYLOAD_SCHEMA) + DOMAIN, SERVICE_SEE, async_see_service, SERVICE_SEE_PAYLOAD_SCHEMA + ) # restore yield from tracker.async_setup_tracked_device() @@ -234,67 +276,109 @@ def async_setup(hass: HomeAssistantType, config: ConfigType): class DeviceTracker: """Representation of a device tracker.""" - def __init__(self, hass: HomeAssistantType, consider_home: timedelta, - track_new: bool, defaults: dict, - devices: Sequence) -> None: + def __init__( + self, + hass: HomeAssistantType, + consider_home: timedelta, + track_new: bool, + defaults: dict, + devices: Sequence, + ) -> None: """Initialize a device tracker.""" self.hass = hass self.devices = {dev.dev_id: dev for dev in devices} self.mac_to_dev = {dev.mac: dev for dev in devices if dev.mac} self.consider_home = consider_home - self.track_new = track_new if track_new is not None \ + self.track_new = ( + track_new + if track_new is not None else defaults.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW) + ) self.defaults = defaults self.group = None self._is_updating = asyncio.Lock(loop=hass.loop) for dev in devices: if self.devices[dev.dev_id] is not dev: - _LOGGER.warning('Duplicate device IDs detected %s', dev.dev_id) + _LOGGER.warning("Duplicate device IDs detected %s", dev.dev_id) if dev.mac and self.mac_to_dev[dev.mac] is not dev: - _LOGGER.warning('Duplicate device MAC addresses detected %s', - dev.mac) + _LOGGER.warning("Duplicate device MAC addresses detected %s", dev.mac) - def see(self, mac: str = None, dev_id: str = None, host_name: str = None, - location_name: str = None, gps: GPSType = None, - gps_accuracy: int = None, battery: int = None, - attributes: dict = None, source_type: str = SOURCE_TYPE_GPS, - picture: str = None, icon: str = None, - consider_home: timedelta = None): + def see( + self, + mac: str = None, + dev_id: str = None, + host_name: str = None, + location_name: str = None, + gps: GPSType = None, + gps_accuracy: int = None, + battery: int = None, + attributes: dict = None, + source_type: str = SOURCE_TYPE_GPS, + picture: str = None, + icon: str = None, + consider_home: timedelta = None, + ): """Notify the device tracker that you see a device.""" self.hass.add_job( - self.async_see(mac, dev_id, host_name, location_name, gps, - gps_accuracy, battery, attributes, source_type, - picture, icon, consider_home) + self.async_see( + mac, + dev_id, + host_name, + location_name, + gps, + gps_accuracy, + battery, + attributes, + source_type, + picture, + icon, + consider_home, + ) ) @asyncio.coroutine def async_see( - self, mac: str = None, dev_id: str = None, host_name: str = None, - location_name: str = None, gps: GPSType = None, - gps_accuracy: int = None, battery: int = None, - attributes: dict = None, source_type: str = SOURCE_TYPE_GPS, - picture: str = None, icon: str = None, - consider_home: timedelta = None): + self, + mac: str = None, + dev_id: str = None, + host_name: str = None, + location_name: str = None, + gps: GPSType = None, + gps_accuracy: int = None, + battery: int = None, + attributes: dict = None, + source_type: str = SOURCE_TYPE_GPS, + picture: str = None, + icon: str = None, + consider_home: timedelta = None, + ): """Notify the device tracker that you see a device. This method is a coroutine. """ if mac is None and dev_id is None: - raise HomeAssistantError('Neither mac or device id passed in') + raise HomeAssistantError("Neither mac or device id passed in") elif mac is not None: mac = str(mac).upper() device = self.mac_to_dev.get(mac) if not device: - dev_id = util.slugify(host_name or '') or util.slugify(mac) + dev_id = util.slugify(host_name or "") or util.slugify(mac) else: dev_id = cv.slug(str(dev_id).lower()) device = self.devices.get(dev_id) if device: yield from device.async_seen( - host_name, location_name, gps, gps_accuracy, battery, - attributes, source_type, consider_home) + host_name, + location_name, + gps, + gps_accuracy, + battery, + attributes, + source_type, + consider_home, + ) if device.track: yield from device.async_update_ha_state() return @@ -302,17 +386,29 @@ class DeviceTracker: # If no device can be found, create it dev_id = util.ensure_unique_string(dev_id, self.devices.keys()) device = Device( - self.hass, consider_home or self.consider_home, self.track_new, - dev_id, mac, (host_name or dev_id).replace('_', ' '), - picture=picture, icon=icon, - hide_if_away=self.defaults.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE)) + self.hass, + consider_home or self.consider_home, + self.track_new, + dev_id, + mac, + (host_name or dev_id).replace("_", " "), + picture=picture, + icon=icon, + hide_if_away=self.defaults.get(CONF_AWAY_HIDE, DEFAULT_AWAY_HIDE), + ) self.devices[dev_id] = device if mac is not None: self.mac_to_dev[mac] = device yield from device.async_seen( - host_name, location_name, gps, gps_accuracy, battery, attributes, - source_type) + host_name, + location_name, + gps, + gps_accuracy, + battery, + attributes, + source_type, + ) if device.track: yield from device.async_update_ha_state() @@ -320,19 +416,26 @@ class DeviceTracker: # During init, we ignore the group if self.group and self.track_new: self.group.async_set_group( - util.slugify(GROUP_NAME_ALL_DEVICES), visible=False, - name=GROUP_NAME_ALL_DEVICES, add=[device.entity_id]) + util.slugify(GROUP_NAME_ALL_DEVICES), + visible=False, + name=GROUP_NAME_ALL_DEVICES, + add=[device.entity_id], + ) - self.hass.bus.async_fire(EVENT_NEW_DEVICE, { - ATTR_ENTITY_ID: device.entity_id, - ATTR_HOST_NAME: device.host_name, - ATTR_MAC: device.mac, - }) + self.hass.bus.async_fire( + EVENT_NEW_DEVICE, + { + ATTR_ENTITY_ID: device.entity_id, + ATTR_HOST_NAME: device.host_name, + ATTR_MAC: device.mac, + }, + ) # update known_devices.yaml self.hass.async_create_task( self.async_update_config( - self.hass.config.path(YAML_DEVICES), dev_id, device) + self.hass.config.path(YAML_DEVICES), dev_id, device + ) ) async def async_update_config(self, path, dev_id, device): @@ -342,8 +445,8 @@ class DeviceTracker: """ async with self._is_updating: await self.hass.async_add_executor_job( - update_config, self.hass.config.path(YAML_DEVICES), - dev_id, device) + update_config, self.hass.config.path(YAML_DEVICES), dev_id, device + ) @callback def async_setup_group(self): @@ -351,13 +454,15 @@ class DeviceTracker: This method must be run in the event loop. """ - entity_ids = [dev.entity_id for dev in self.devices.values() - if dev.track] + entity_ids = [dev.entity_id for dev in self.devices.values() if dev.track] self.group = self.hass.components.group self.group.async_set_group( - util.slugify(GROUP_NAME_ALL_DEVICES), visible=False, - name=GROUP_NAME_ALL_DEVICES, entity_ids=entity_ids) + util.slugify(GROUP_NAME_ALL_DEVICES), + visible=False, + name=GROUP_NAME_ALL_DEVICES, + entity_ids=entity_ids, + ) @callback def async_update_stale(self, now: dt_util.dt.datetime): @@ -366,8 +471,7 @@ class DeviceTracker: This method must be run in the event loop. """ for device in self.devices.values(): - if (device.track and device.last_update_home) and \ - device.stale(now): + if (device.track and device.last_update_home) and device.stale(now): self.hass.async_add_job(device.async_update_ha_state(True)) @asyncio.coroutine @@ -376,6 +480,7 @@ class DeviceTracker: This method is a coroutine. """ + @asyncio.coroutine def async_init_single_device(dev): """Init a single device_tracker entity.""" @@ -385,8 +490,7 @@ class DeviceTracker: tasks = [] for device in self.devices.values(): if device.track and not device.last_seen: - tasks.append(self.hass.async_add_job( - async_init_single_device(device))) + tasks.append(self.hass.async_add_job(async_init_single_device(device))) if tasks: yield from asyncio.wait(tasks, loop=self.hass.loop) @@ -409,10 +513,19 @@ class Device(Entity): last_update_home = False _state = STATE_NOT_HOME - def __init__(self, hass: HomeAssistantType, consider_home: timedelta, - track: bool, dev_id: str, mac: str, name: str = None, - picture: str = None, gravatar: str = None, icon: str = None, - hide_if_away: bool = False) -> None: + def __init__( + self, + hass: HomeAssistantType, + consider_home: timedelta, + track: bool, + dev_id: str, + mac: str, + name: str = None, + picture: str = None, + gravatar: str = None, + icon: str = None, + hide_if_away: bool = False, + ) -> None: """Initialize a device.""" self.hass = hass self.entity_id = ENTITY_ID_FORMAT.format(dev_id) @@ -463,9 +576,7 @@ class Device(Entity): @property def state_attributes(self): """Return the device state attributes.""" - attr = { - ATTR_SOURCE_TYPE: self.source_type - } + attr = {ATTR_SOURCE_TYPE: self.source_type} if self.gps: attr[ATTR_LATITUDE] = self.gps[0] @@ -488,11 +599,17 @@ class Device(Entity): return self.away_hide and self.state != STATE_HOME @asyncio.coroutine - def async_seen(self, host_name: str = None, location_name: str = None, - gps: GPSType = None, gps_accuracy=0, battery: int = None, - attributes: dict = None, - source_type: str = SOURCE_TYPE_GPS, - consider_home: timedelta = None): + def async_seen( + self, + host_name: str = None, + location_name: str = None, + gps: GPSType = None, + gps_accuracy=0, + battery: int = None, + attributes: dict = None, + source_type: str = SOURCE_TYPE_GPS, + consider_home: timedelta = None, + ): """Mark the device as seen.""" self.source_type = source_type self.last_seen = dt_util.utcnow() @@ -515,7 +632,8 @@ class Device(Entity): self.gps = None self.gps_accuracy = 0 _LOGGER.warning( - "Could not parse gps value for %s: %s", self.dev_id, gps) + "Could not parse gps value for %s: %s", self.dev_id, gps + ) # pylint: disable=not-an-iterable yield from self.async_update() @@ -525,8 +643,10 @@ class Device(Entity): Async friendly. """ - return self.last_seen and \ - (now or dt_util.utcnow()) - self.last_seen > self.consider_home + return ( + self.last_seen + and (now or dt_util.utcnow()) - self.last_seen > self.consider_home + ) @asyncio.coroutine def async_update(self): @@ -540,7 +660,8 @@ class Device(Entity): self._state = self.location_name elif self.gps is not None and self.source_type == SOURCE_TYPE_GPS: zone_state = async_active_zone( - self.hass, self.gps[0], self.gps[1], self.gps_accuracy) + self.hass, self.gps[0], self.gps[1], self.gps_accuracy + ) if zone_state is None: self._state = STATE_NOT_HOME elif zone_state.entity_id == zone.ENTITY_ID_HOME: @@ -564,16 +685,18 @@ class Device(Entity): self._state = state.state for attr, var in ( - (ATTR_SOURCE_TYPE, 'source_type'), - (ATTR_GPS_ACCURACY, 'gps_accuracy'), - (ATTR_BATTERY, 'battery'), + (ATTR_SOURCE_TYPE, "source_type"), + (ATTR_GPS_ACCURACY, "gps_accuracy"), + (ATTR_BATTERY, "battery"), ): if attr in state.attributes: setattr(self, var, state.attributes[attr]) if ATTR_LONGITUDE in state.attributes: - self.gps = (state.attributes[ATTR_LATITUDE], - state.attributes[ATTR_LONGITUDE]) + self.gps = ( + state.attributes[ATTR_LATITUDE], + state.attributes[ATTR_LONGITUDE], + ) class DeviceScanner: @@ -618,43 +741,46 @@ class DeviceScanner: def load_config(path: str, hass: HomeAssistantType, consider_home: timedelta): """Load devices from YAML configuration file.""" return run_coroutine_threadsafe( - async_load_config(path, hass, consider_home), hass.loop).result() + async_load_config(path, hass, consider_home), hass.loop + ).result() @asyncio.coroutine -def async_load_config(path: str, hass: HomeAssistantType, - consider_home: timedelta): +def async_load_config(path: str, hass: HomeAssistantType, consider_home: timedelta): """Load devices from YAML configuration file. This method is a coroutine. """ - dev_schema = vol.Schema({ - vol.Required(CONF_NAME): cv.string, - vol.Optional(CONF_ICON, default=None): vol.Any(None, cv.icon), - vol.Optional('track', default=False): cv.boolean, - vol.Optional(CONF_MAC, default=None): - vol.Any(None, vol.All(cv.string, vol.Upper)), - vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean, - vol.Optional('gravatar', default=None): vol.Any(None, cv.string), - vol.Optional('picture', default=None): vol.Any(None, cv.string), - vol.Optional(CONF_CONSIDER_HOME, default=consider_home): vol.All( - cv.time_period, cv.positive_timedelta), - }) + dev_schema = vol.Schema( + { + vol.Required(CONF_NAME): cv.string, + vol.Optional(CONF_ICON, default=None): vol.Any(None, cv.icon), + vol.Optional("track", default=False): cv.boolean, + vol.Optional(CONF_MAC, default=None): vol.Any( + None, vol.All(cv.string, vol.Upper) + ), + vol.Optional(CONF_AWAY_HIDE, default=DEFAULT_AWAY_HIDE): cv.boolean, + vol.Optional("gravatar", default=None): vol.Any(None, cv.string), + vol.Optional("picture", default=None): vol.Any(None, cv.string), + vol.Optional(CONF_CONSIDER_HOME, default=consider_home): vol.All( + cv.time_period, cv.positive_timedelta + ), + } + ) try: result = [] try: - devices = yield from hass.async_add_job( - load_yaml_config_file, path) + devices = yield from hass.async_add_job(load_yaml_config_file, path) except HomeAssistantError as err: _LOGGER.error("Unable to load %s: %s", path, str(err)) return [] for dev_id, device in devices.items(): # Deprecated option. We just ignore it to avoid breaking change - device.pop('vendor', None) + device.pop("vendor", None) try: device = dev_schema(device) - device['dev_id'] = cv.slugify(dev_id) + device["dev_id"] = cv.slugify(dev_id) except vol.Invalid as exp: async_log_exception(exp, dev_id, devices, hass) else: @@ -666,9 +792,13 @@ def async_load_config(path: str, hass: HomeAssistantType, @callback -def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType, - scanner: Any, async_see_device: Callable, - platform: str): +def async_setup_scanner_platform( + hass: HomeAssistantType, + config: ConfigType, + scanner: Any, + async_see_device: Callable, + platform: str, +): """Set up the connect scanner-based platform to device tracker. This method must be run in the event loop. @@ -685,7 +815,10 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType, if update_lock.locked(): _LOGGER.warning( "Updating device list from %s took longer than the scheduled " - "scan interval %s", platform, interval) + "scan interval %s", + platform, + interval, + ) return async with update_lock: @@ -699,26 +832,27 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType, seen.add(mac) try: - extra_attributes = (await - scanner.async_get_extra_attributes(mac)) + extra_attributes = await scanner.async_get_extra_attributes(mac) except NotImplementedError: extra_attributes = dict() kwargs = { - 'mac': mac, - 'host_name': host_name, - 'source_type': SOURCE_TYPE_ROUTER, - 'attributes': { - 'scanner': scanner.__class__.__name__, - **extra_attributes - } + "mac": mac, + "host_name": host_name, + "source_type": SOURCE_TYPE_ROUTER, + "attributes": { + "scanner": scanner.__class__.__name__, + **extra_attributes, + }, } zone_home = hass.states.get(zone.ENTITY_ID_HOME) if zone_home: - kwargs['gps'] = [zone_home.attributes[ATTR_LATITUDE], - zone_home.attributes[ATTR_LONGITUDE]] - kwargs['gps_accuracy'] = 0 + kwargs["gps"] = [ + zone_home.attributes[ATTR_LATITUDE], + zone_home.attributes[ATTR_LONGITUDE], + ] + kwargs["gps_accuracy"] = 0 hass.async_add_job(async_see_device(**kwargs)) @@ -728,16 +862,18 @@ def async_setup_scanner_platform(hass: HomeAssistantType, config: ConfigType, def update_config(path: str, dev_id: str, device: Device): """Add device to YAML configuration file.""" - with open(path, 'a') as out: - device = {device.dev_id: { - ATTR_NAME: device.name, - ATTR_MAC: device.mac, - ATTR_ICON: device.icon, - 'picture': device.config_picture, - 'track': device.track, - CONF_AWAY_HIDE: device.away_hide, - }} - out.write('\n') + with open(path, "a") as out: + device = { + device.dev_id: { + ATTR_NAME: device.name, + ATTR_MAC: device.mac, + ATTR_ICON: device.icon, + "picture": device.config_picture, + "track": device.track, + CONF_AWAY_HIDE: device.away_hide, + } + } + out.write("\n") out.write(dump(device)) @@ -747,5 +883,6 @@ def get_gravatar_for_email(email: str): Async friendly. """ import hashlib - url = 'https://www.gravatar.com/avatar/{}.jpg?s=80&d=wavatar' - return url.format(hashlib.md5(email.encode('utf-8').lower()).hexdigest()) + + url = "https://www.gravatar.com/avatar/{}.jpg?s=80&d=wavatar" + return url.format(hashlib.md5(email.encode("utf-8").lower()).hexdigest()) diff --git a/homeassistant/components/device_tracker/actiontec.py b/homeassistant/components/device_tracker/actiontec.py index 72d9992c6..a9e2dba50 100644 --- a/homeassistant/components/device_tracker/actiontec.py +++ b/homeassistant/components/device_tracker/actiontec.py @@ -13,22 +13,28 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv import homeassistant.util.dt as dt_util from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME _LOGGER = logging.getLogger(__name__) _LEASES_REGEX = re.compile( - r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})' + - r'\smac:\s(?P([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))' + - r'\svalid\sfor:\s(?P(-?\d+))' + - r'\ssec') + r"(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})" + + r"\smac:\s(?P([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))" + + r"\svalid\sfor:\s(?P(-?\d+))" + + r"\ssec" +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + } +) def get_scanner(hass, config): @@ -37,7 +43,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -Device = namedtuple('Device', ['mac', 'ip', 'last_update']) +Device = namedtuple("Device", ["mac", "ip", "last_update"]) class ActiontecDeviceScanner(DeviceScanner): @@ -80,9 +86,11 @@ class ActiontecDeviceScanner(DeviceScanner): actiontec_data = self.get_actiontec_data() if not actiontec_data: return False - self.last_results = [Device(data['mac'], name, now) - for name, data in actiontec_data.items() - if data['timevalid'] > -60] + self.last_results = [ + Device(data["mac"], name, now) + for name, data in actiontec_data.items() + if data["timevalid"] > -60 + ] _LOGGER.info("Scan successful") return True @@ -90,17 +98,16 @@ class ActiontecDeviceScanner(DeviceScanner): """Retrieve data from Actiontec MI424WR and return parsed result.""" try: telnet = telnetlib.Telnet(self.host) - telnet.read_until(b'Username: ') - telnet.write((self.username + '\n').encode('ascii')) - telnet.read_until(b'Password: ') - telnet.write((self.password + '\n').encode('ascii')) - prompt = telnet.read_until( - b'Wireless Broadband Router> ').split(b'\n')[-1] - telnet.write('firewall mac_cache_dump\n'.encode('ascii')) - telnet.write('\n'.encode('ascii')) + telnet.read_until(b"Username: ") + telnet.write((self.username + "\n").encode("ascii")) + telnet.read_until(b"Password: ") + telnet.write((self.password + "\n").encode("ascii")) + prompt = telnet.read_until(b"Wireless Broadband Router> ").split(b"\n")[-1] + telnet.write("firewall mac_cache_dump\n".encode("ascii")) + telnet.write("\n".encode("ascii")) telnet.read_until(prompt) - leases_result = telnet.read_until(prompt).split(b'\n')[1:-1] - telnet.write('exit\n'.encode('ascii')) + leases_result = telnet.read_until(prompt).split(b"\n")[1:-1] + telnet.write("exit\n".encode("ascii")) except EOFError: _LOGGER.exception("Unexpected response from router") return @@ -110,11 +117,11 @@ class ActiontecDeviceScanner(DeviceScanner): devices = {} for lease in leases_result: - match = _LEASES_REGEX.search(lease.decode('utf-8')) + match = _LEASES_REGEX.search(lease.decode("utf-8")) if match is not None: - devices[match.group('ip')] = { - 'ip': match.group('ip'), - 'mac': match.group('mac').upper(), - 'timevalid': int(match.group('timevalid')) - } + devices[match.group("ip")] = { + "ip": match.group("ip"), + "mac": match.group("mac").upper(), + "timevalid": int(match.group("timevalid")), + } return devices diff --git a/homeassistant/components/device_tracker/aruba.py b/homeassistant/components/device_tracker/aruba.py index 142842b12..363f5194d 100644 --- a/homeassistant/components/device_tracker/aruba.py +++ b/homeassistant/components/device_tracker/aruba.py @@ -11,23 +11,29 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pexpect==4.6.0'] +REQUIREMENTS = ["pexpect==4.6.0"] _DEVICES_REGEX = re.compile( - r'(?P([^\s]+)?)\s+' + - r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+' + - r'(?P([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))\s+') + r"(?P([^\s]+)?)\s+" + + r"(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s+" + + r"(?P([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))\s+" +) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + } +) def get_scanner(hass, config): @@ -55,15 +61,15 @@ class ArubaDeviceScanner(DeviceScanner): def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" self._update_info() - return [client['mac'] for client in self.last_results] + return [client["mac"] for client in self.last_results] def get_device_name(self, device): """Return the name of the given device or None if we don't know.""" if not self.last_results: return None for client in self.last_results: - if client['mac'] == device: - return client['name'] + if client["mac"] == device: + return client["name"] return None def _update_info(self): @@ -84,13 +90,21 @@ class ArubaDeviceScanner(DeviceScanner): def get_aruba_data(self): """Retrieve data from Aruba Access Point and return parsed result.""" import pexpect - connect = 'ssh {}@{}' + + connect = "ssh {}@{}" ssh = pexpect.spawn(connect.format(self.username, self.host)) - query = ssh.expect(['password:', pexpect.TIMEOUT, pexpect.EOF, - 'continue connecting (yes/no)?', - 'Host key verification failed.', - 'Connection refused', - 'Connection timed out'], timeout=120) + query = ssh.expect( + [ + "password:", + pexpect.TIMEOUT, + pexpect.EOF, + "continue connecting (yes/no)?", + "Host key verification failed.", + "Connection refused", + "Connection timed out", + ], + timeout=120, + ) if query == 1: _LOGGER.error("Timeout") return @@ -98,8 +112,8 @@ class ArubaDeviceScanner(DeviceScanner): _LOGGER.error("Unexpected response from router") return if query == 3: - ssh.sendline('yes') - ssh.expect('password:') + ssh.sendline("yes") + ssh.expect("password:") elif query == 4: _LOGGER.error("Host key changed") return @@ -110,19 +124,19 @@ class ArubaDeviceScanner(DeviceScanner): _LOGGER.error("Connection timed out") return ssh.sendline(self.password) - ssh.expect('#') - ssh.sendline('show clients') - ssh.expect('#') - devices_result = ssh.before.split(b'\r\n') - ssh.sendline('exit') + ssh.expect("#") + ssh.sendline("show clients") + ssh.expect("#") + devices_result = ssh.before.split(b"\r\n") + ssh.sendline("exit") devices = {} for device in devices_result: - match = _DEVICES_REGEX.search(device.decode('utf-8')) + match = _DEVICES_REGEX.search(device.decode("utf-8")) if match: - devices[match.group('ip')] = { - 'ip': match.group('ip'), - 'mac': match.group('mac').upper(), - 'name': match.group('name') + devices[match.group("ip")] = { + "ip": match.group("ip"), + "mac": match.group("mac").upper(), + "name": match.group("name"), } return devices diff --git a/homeassistant/components/device_tracker/asuswrt.py b/homeassistant/components/device_tracker/asuswrt.py index 710a07f77..44f4a0827 100644 --- a/homeassistant/components/device_tracker/asuswrt.py +++ b/homeassistant/components/device_tracker/asuswrt.py @@ -14,68 +14,80 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT, CONF_MODE, - CONF_PROTOCOL) + CONF_HOST, + CONF_PASSWORD, + CONF_USERNAME, + CONF_PORT, + CONF_MODE, + CONF_PROTOCOL, +) -REQUIREMENTS = ['pexpect==4.6.0'] +REQUIREMENTS = ["pexpect==4.6.0"] _LOGGER = logging.getLogger(__name__) -CONF_PUB_KEY = 'pub_key' -CONF_SSH_KEY = 'ssh_key' -CONF_REQUIRE_IP = 'require_ip' +CONF_PUB_KEY = "pub_key" +CONF_SSH_KEY = "ssh_key" +CONF_REQUIRE_IP = "require_ip" DEFAULT_SSH_PORT = 22 -SECRET_GROUP = 'Password or SSH Key' +SECRET_GROUP = "Password or SSH Key" PLATFORM_SCHEMA = vol.All( cv.has_at_least_one_key(CONF_PASSWORD, CONF_PUB_KEY, CONF_SSH_KEY), - PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_PROTOCOL, default='ssh'): vol.In(['ssh', 'telnet']), - vol.Optional(CONF_MODE, default='router'): vol.In(['router', 'ap']), - vol.Optional(CONF_PORT, default=DEFAULT_SSH_PORT): cv.port, - vol.Optional(CONF_REQUIRE_IP, default=True): cv.boolean, - vol.Exclusive(CONF_PASSWORD, SECRET_GROUP): cv.string, - vol.Exclusive(CONF_SSH_KEY, SECRET_GROUP): cv.isfile, - vol.Exclusive(CONF_PUB_KEY, SECRET_GROUP): cv.isfile - })) + PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_PROTOCOL, default="ssh"): vol.In(["ssh", "telnet"]), + vol.Optional(CONF_MODE, default="router"): vol.In(["router", "ap"]), + vol.Optional(CONF_PORT, default=DEFAULT_SSH_PORT): cv.port, + vol.Optional(CONF_REQUIRE_IP, default=True): cv.boolean, + vol.Exclusive(CONF_PASSWORD, SECRET_GROUP): cv.string, + vol.Exclusive(CONF_SSH_KEY, SECRET_GROUP): cv.isfile, + vol.Exclusive(CONF_PUB_KEY, SECRET_GROUP): cv.isfile, + } + ), +) -_LEASES_CMD = 'cat /var/lib/misc/dnsmasq.leases' +_LEASES_CMD = "cat /var/lib/misc/dnsmasq.leases" _LEASES_REGEX = re.compile( - r'\w+\s' + - r'(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s' + - r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s' + - r'(?P([^\s]+))') + r"\w+\s" + + r"(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))\s" + + r"(?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\s" + + r"(?P([^\s]+))" +) # Command to get both 5GHz and 2.4GHz clients -_WL_CMD = 'for dev in `nvram get wl_ifnames`; do wl -i $dev assoclist; done' -_WL_REGEX = re.compile( - r'\w+\s' + - r'(?P(([0-9A-F]{2}[:-]){5}([0-9A-F]{2})))') +_WL_CMD = "for dev in `nvram get wl_ifnames`; do wl -i $dev assoclist; done" +_WL_REGEX = re.compile(r"\w+\s" + r"(?P(([0-9A-F]{2}[:-]){5}([0-9A-F]{2})))") -_IP_NEIGH_CMD = 'ip neigh' +_IP_NEIGH_CMD = "ip neigh" _IP_NEIGH_REGEX = re.compile( - r'(?P([0-9]{1,3}[\.]){3}[0-9]{1,3}|' - r'([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{1,4}){1,7})\s' - r'\w+\s' - r'\w+\s' - r'(\w+\s(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))))?\s' - r'\s?(router)?' - r'\s?(nud)?' - r'(?P(\w+))') + r"(?P([0-9]{1,3}[\.]){3}[0-9]{1,3}|" + r"([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{0,4}(:[0-9a-fA-F]{1,4}){1,7})\s" + r"\w+\s" + r"\w+\s" + r"(\w+\s(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2}))))?\s" + r"\s?(router)?" + r"\s?(nud)?" + r"(?P(\w+))" +) -_ARP_CMD = 'arp -n' +_ARP_CMD = "arp -n" _ARP_REGEX = re.compile( - r'.+\s' + - r'\((?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\)\s' + - r'.+\s' + - r'(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))' + - r'\s' + - r'.*') + r".+\s" + + r"\((?P([0-9]{1,3}[\.]){3}[0-9]{1,3})\)\s" + + r".+\s" + + r"(?P(([0-9a-f]{2}[:-]){5}([0-9a-f]{2})))" + + r"\s" + + r".*" +) def get_scanner(hass, config): @@ -100,7 +112,7 @@ def _parse_lines(lines, regex): return results -Device = namedtuple('Device', ['mac', 'ip', 'name']) +Device = namedtuple("Device", ["mac", "ip", "name"]) class AsusWrtDeviceScanner(DeviceScanner): @@ -111,20 +123,21 @@ class AsusWrtDeviceScanner(DeviceScanner): """Initialize the scanner.""" self.host = config[CONF_HOST] self.username = config[CONF_USERNAME] - self.password = config.get(CONF_PASSWORD, '') - self.ssh_key = config.get('ssh_key', config.get('pub_key', '')) + self.password = config.get(CONF_PASSWORD, "") + self.ssh_key = config.get("ssh_key", config.get("pub_key", "")) self.protocol = config[CONF_PROTOCOL] self.mode = config[CONF_MODE] self.port = config[CONF_PORT] self.require_ip = config[CONF_REQUIRE_IP] - if self.protocol == 'ssh': + if self.protocol == "ssh": self.connection = SshConnection( - self.host, self.port, self.username, self.password, - self.ssh_key) + self.host, self.port, self.username, self.password, self.ssh_key + ) else: self.connection = TelnetConnection( - self.host, self.port, self.username, self.password) + self.host, self.port, self.username, self.password + ) self.last_results = {} @@ -151,7 +164,7 @@ class AsusWrtDeviceScanner(DeviceScanner): if not self.success_init: return False - _LOGGER.info('Checking Devices') + _LOGGER.info("Checking Devices") data = self.get_asuswrt_data() if not data: return False @@ -169,7 +182,7 @@ class AsusWrtDeviceScanner(DeviceScanner): devices.update(self._get_wl()) devices.update(self._get_arp()) devices.update(self._get_neigh(devices)) - if not self.mode == 'ap': + if not self.mode == "ap": devices.update(self._get_leases(devices)) ret_devices = {} @@ -185,7 +198,7 @@ class AsusWrtDeviceScanner(DeviceScanner): result = _parse_lines(lines, _WL_REGEX) devices = {} for device in result: - mac = device['mac'].upper() + mac = device["mac"].upper() devices[mac] = Device(mac, None, None) return devices @@ -193,18 +206,18 @@ class AsusWrtDeviceScanner(DeviceScanner): lines = self.connection.run_command(_LEASES_CMD) if not lines: return {} - lines = [line for line in lines if not line.startswith('duid ')] + lines = [line for line in lines if not line.startswith("duid ")] result = _parse_lines(lines, _LEASES_REGEX) devices = {} for device in result: # For leases where the client doesn't set a hostname, ensure it # is blank and not '*', which breaks entity_id down the line. - host = device['host'] - if host == '*': - host = '' - mac = device['mac'].upper() + host = device["host"] + if host == "*": + host = "" + mac = device["mac"].upper() if mac in cur_devices: - devices[mac] = Device(mac, device['ip'], host) + devices[mac] = Device(mac, device["ip"], host) return devices def _get_neigh(self, cur_devices): @@ -214,14 +227,14 @@ class AsusWrtDeviceScanner(DeviceScanner): result = _parse_lines(lines, _IP_NEIGH_REGEX) devices = {} for device in result: - status = device['status'] - if status is None or status.upper() != 'REACHABLE': + status = device["status"] + if status is None or status.upper() != "REACHABLE": continue - if device['mac'] is not None: - mac = device['mac'].upper() + if device["mac"] is not None: + mac = device["mac"].upper() old_device = cur_devices.get(mac) old_ip = old_device.ip if old_device else None - devices[mac] = Device(mac, device.get('ip', old_ip), None) + devices[mac] = Device(mac, device.get("ip", old_ip), None) return devices def _get_arp(self): @@ -231,9 +244,9 @@ class AsusWrtDeviceScanner(DeviceScanner): result = _parse_lines(lines, _ARP_REGEX) devices = {} for device in result: - if device['mac'] is not None: - mac = device['mac'].upper() - devices[mac] = Device(mac, device['ip'], None) + if device["mac"] is not None: + mac = device["mac"].upper() + devices[mac] = Device(mac, device["ip"], None) return devices @@ -282,8 +295,8 @@ class SshConnection(_Connection): self.connect() self._ssh.sendline(command) self._ssh.prompt() - lines = self._ssh.before.split(b'\n')[1:-1] - return [line.decode('utf-8') for line in lines] + lines = self._ssh.before.split(b"\n")[1:-1] + return [line.decode("utf-8") for line in lines] except exceptions.EOF as err: _LOGGER.error("Connection refused. %s", self._ssh.before) self.disconnect() @@ -303,11 +316,21 @@ class SshConnection(_Connection): self._ssh = pxssh.pxssh() if self._ssh_key: - self._ssh.login(self._host, self._username, quiet=False, - ssh_key=self._ssh_key, port=self._port) + self._ssh.login( + self._host, + self._username, + quiet=False, + ssh_key=self._ssh_key, + port=self._port, + ) else: - self._ssh.login(self._host, self._username, quiet=False, - password=self._password, port=self._port) + self._ssh.login( + self._host, + self._username, + quiet=False, + password=self._password, + port=self._port, + ) super().connect() @@ -346,10 +369,9 @@ class TelnetConnection(_Connection): try: if not self.connected: self.connect() - self._telnet.write('{}\n'.format(command).encode('ascii')) - data = (self._telnet.read_until(self._prompt_string). - split(b'\n')[1:-1]) - return [line.decode('utf-8') for line in data] + self._telnet.write("{}\n".format(command).encode("ascii")) + data = self._telnet.read_until(self._prompt_string).split(b"\n")[1:-1] + return [line.decode("utf-8") for line in data] except EOFError: _LOGGER.error("Unexpected response from router") self.disconnect() @@ -370,18 +392,18 @@ class TelnetConnection(_Connection): def connect(self): """Connect to the ASUS-WRT Telnet server.""" self._telnet = telnetlib.Telnet(self._host) - self._telnet.read_until(b'login: ') - self._telnet.write((self._username + '\n').encode('ascii')) - self._telnet.read_until(b'Password: ') - self._telnet.write((self._password + '\n').encode('ascii')) - self._prompt_string = self._telnet.read_until(b'#').split(b'\n')[-1] + self._telnet.read_until(b"login: ") + self._telnet.write((self._username + "\n").encode("ascii")) + self._telnet.read_until(b"Password: ") + self._telnet.write((self._password + "\n").encode("ascii")) + self._prompt_string = self._telnet.read_until(b"#").split(b"\n")[-1] super().connect() def disconnect(self): """Disconnect the current Telnet connection.""" try: - self._telnet.write('exit\n'.encode('ascii')) + self._telnet.write("exit\n".encode("ascii")) except Exception: # pylint: disable=broad-except pass diff --git a/homeassistant/components/device_tracker/automatic.py b/homeassistant/components/device_tracker/automatic.py index 4fcc550d7..33a221048 100644 --- a/homeassistant/components/device_tracker/automatic.py +++ b/homeassistant/components/device_tracker/automatic.py @@ -14,8 +14,14 @@ from aiohttp import web import voluptuous as vol from homeassistant.components.device_tracker import ( - ATTR_ATTRIBUTES, ATTR_DEV_ID, ATTR_GPS, ATTR_GPS_ACCURACY, ATTR_HOST_NAME, - ATTR_MAC, PLATFORM_SCHEMA) + ATTR_ATTRIBUTES, + ATTR_DEV_ID, + ATTR_GPS, + ATTR_GPS_ACCURACY, + ATTR_HOST_NAME, + ATTR_MAC, + PLATFORM_SCHEMA, +) from homeassistant.components.http import HomeAssistantView from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.core import callback @@ -23,34 +29,36 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_time_interval -REQUIREMENTS = ['aioautomatic==0.6.5'] +REQUIREMENTS = ["aioautomatic==0.6.5"] _LOGGER = logging.getLogger(__name__) -ATTR_FUEL_LEVEL = 'fuel_level' -AUTOMATIC_CONFIG_FILE = '.automatic/session-{}.json' +ATTR_FUEL_LEVEL = "fuel_level" +AUTOMATIC_CONFIG_FILE = ".automatic/session-{}.json" -CONF_CLIENT_ID = 'client_id' -CONF_CURRENT_LOCATION = 'current_location' -CONF_DEVICES = 'devices' -CONF_SECRET = 'secret' +CONF_CLIENT_ID = "client_id" +CONF_CURRENT_LOCATION = "current_location" +CONF_DEVICES = "devices" +CONF_SECRET = "secret" -DATA_CONFIGURING = 'automatic_configurator_clients' -DATA_REFRESH_TOKEN = 'refresh_token' -DEFAULT_SCOPE = ['location', 'trip', 'vehicle:events', 'vehicle:profile'] +DATA_CONFIGURING = "automatic_configurator_clients" +DATA_REFRESH_TOKEN = "refresh_token" +DEFAULT_SCOPE = ["location", "trip", "vehicle:events", "vehicle:profile"] DEFAULT_TIMEOUT = 5 -DEPENDENCIES = ['http'] +DEPENDENCIES = ["http"] -EVENT_AUTOMATIC_UPDATE = 'automatic_update' +EVENT_AUTOMATIC_UPDATE = "automatic_update" -FULL_SCOPE = DEFAULT_SCOPE + ['current_location'] +FULL_SCOPE = DEFAULT_SCOPE + ["current_location"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_CLIENT_ID): cv.string, - vol.Required(CONF_SECRET): cv.string, - vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean, - vol.Optional(CONF_DEVICES): vol.All(cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_CLIENT_ID): cv.string, + vol.Required(CONF_SECRET): cv.string, + vol.Optional(CONF_CURRENT_LOCATION, default=False): cv.boolean, + vol.Optional(CONF_DEVICES): vol.All(cv.ensure_list, [cv.string]), + } +) def _get_refresh_token_from_file(hass, filename): @@ -76,10 +84,8 @@ def _write_refresh_token_to_file(hass, filename, refresh_token): path = hass.config.path(filename) os.makedirs(os.path.dirname(path), exist_ok=True) - with open(path, 'w+') as data_file: - json.dump({ - DATA_REFRESH_TOKEN: refresh_token - }, data_file) + with open(path, "w+") as data_file: + json.dump({DATA_REFRESH_TOKEN: refresh_token}, data_file) @asyncio.coroutine @@ -95,20 +101,21 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): client_id=config[CONF_CLIENT_ID], client_secret=config[CONF_SECRET], client_session=async_get_clientsession(hass), - request_kwargs={'timeout': DEFAULT_TIMEOUT}) + request_kwargs={"timeout": DEFAULT_TIMEOUT}, + ) filename = AUTOMATIC_CONFIG_FILE.format(config[CONF_CLIENT_ID]) refresh_token = yield from hass.async_add_job( - _get_refresh_token_from_file, hass, filename) + _get_refresh_token_from_file, hass, filename + ) @asyncio.coroutine def initialize_data(session): """Initialize the AutomaticData object from the created session.""" hass.async_add_job( - _write_refresh_token_to_file, hass, filename, - session.refresh_token) - data = AutomaticData( - hass, client, session, config.get(CONF_DEVICES), async_see) + _write_refresh_token_to_file, hass, filename, session.refresh_token + ) + data = AutomaticData(hass, client, session, config.get(CONF_DEVICES), async_see) # Load the initial vehicle data vehicles = yield from session.get_vehicles() @@ -121,8 +128,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): if refresh_token is not None: try: - session = yield from client.create_session_from_refresh_token( - refresh_token) + session = yield from client.create_session_from_refresh_token(refresh_token) yield from initialize_data(session) return True except aioautomatic.exceptions.AutomaticError as err: @@ -130,8 +136,8 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): configurator = hass.components.configurator request_id = configurator.async_request_config( - "Automatic", description=( - "Authorization required for Automatic device tracker."), + "Automatic", + description=("Authorization required for Automatic device tracker."), link_name="Click here to authorize Home Assistant.", link_url=client.generate_oauth_url(scope), entity_picture="/static/images/logo_automatic.png", @@ -141,8 +147,7 @@ def async_setup_scanner(hass, config, async_see, discovery_info=None): def initialize_callback(code, state): """Call after OAuth2 response is returned.""" try: - session = yield from client.create_session_from_oauth_code( - code, state) + session = yield from client.create_session_from_oauth_code(code, state) yield from initialize_data(session) configurator.async_request_done(request_id) except aioautomatic.exceptions.AutomaticError as err: @@ -161,32 +166,32 @@ class AutomaticAuthCallbackView(HomeAssistantView): """Handle OAuth finish callback requests.""" requires_auth = False - url = '/api/automatic/callback' - name = 'api:automatic:callback' + url = "/api/automatic/callback" + name = "api:automatic:callback" @callback def get(self, request): # pylint: disable=no-self-use """Finish OAuth callback request.""" - hass = request.app['hass'] + hass = request.app["hass"] params = request.query - response = web.HTTPFound('/states') + response = web.HTTPFound("/states") - if 'state' not in params or 'code' not in params: - if 'error' in params: - _LOGGER.error( - "Error authorizing Automatic: %s", params['error']) + if "state" not in params or "code" not in params: + if "error" in params: + _LOGGER.error("Error authorizing Automatic: %s", params["error"]) return response - _LOGGER.error( - "Error authorizing Automatic. Invalid response returned") + _LOGGER.error("Error authorizing Automatic. Invalid response returned") return response - if DATA_CONFIGURING not in hass.data or \ - params['state'] not in hass.data[DATA_CONFIGURING]: + if ( + DATA_CONFIGURING not in hass.data + or params["state"] not in hass.data[DATA_CONFIGURING] + ): _LOGGER.error("Automatic configuration request not found") return response - code = params['code'] - state = params['state'] + code = params["code"] + state = params["state"] initialize_callback = hass.data[DATA_CONFIGURING][state] hass.async_add_job(initialize_callback(code, state)) @@ -209,8 +214,8 @@ class AutomaticData: self.ws_close_requested = False self.client.on_app_event( - lambda name, event: self.hass.async_add_job( - self.handle_event(name, event))) + lambda name, event: self.hass.async_add_job(self.handle_event(name, event)) + ) hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, self.ws_close()) @@ -234,9 +239,11 @@ class AutomaticData: if event.created_at < self.vehicle_seen[event.vehicle.id]: # Skip events received out of order - _LOGGER.debug("Skipping out of order event. Event Created %s. " - "Last seen event: %s", event.created_at, - self.vehicle_seen[event.vehicle.id]) + _LOGGER.debug( + "Skipping out of order event. Event Created %s. " "Last seen event: %s", + event.created_at, + self.vehicle_seen[event.vehicle.id], + ) return self.vehicle_seen[event.vehicle.id] = event.created_at @@ -262,6 +269,7 @@ class AutomaticData: def ws_connect(self, now=None): """Open the websocket connection.""" import aioautomatic + self.ws_close_requested = False if self.ws_reconnect_handle is not None: @@ -269,16 +277,19 @@ class AutomaticData: try: ws_loop_future = yield from self.client.ws_connect() except aioautomatic.exceptions.UnauthorizedClientError: - _LOGGER.error("Client unauthorized for websocket connection. " - "Ensure Websocket is selected in the Automatic " - "developer application event delivery preferences") + _LOGGER.error( + "Client unauthorized for websocket connection. " + "Ensure Websocket is selected in the Automatic " + "developer application event delivery preferences" + ) return except aioautomatic.exceptions.AutomaticError as err: if self.ws_reconnect_handle is None: # Show log error and retry connection every 5 minutes _LOGGER.error("Error opening websocket connection: %s", err) self.ws_reconnect_handle = async_track_time_interval( - self.hass, self.ws_connect, timedelta(minutes=5)) + self.hass, self.ws_connect, timedelta(minutes=5) + ) return if self.ws_reconnect_handle is not None: @@ -321,8 +332,9 @@ class AutomaticData: name = vehicle.display_name if name is None: - name = ' '.join(filter(None, ( - str(vehicle.year), vehicle.make, vehicle.model))) + name = " ".join( + filter(None, (str(vehicle.year), vehicle.make, vehicle.model)) + ) if self.devices is not None and name not in self.devices: self.vehicle_info[vehicle.id] = None @@ -332,12 +344,9 @@ class AutomaticData: ATTR_DEV_ID: vehicle.id, ATTR_HOST_NAME: name, ATTR_MAC: vehicle.id, - ATTR_ATTRIBUTES: { - ATTR_FUEL_LEVEL: vehicle.fuel_level_percent, - } + ATTR_ATTRIBUTES: {ATTR_FUEL_LEVEL: vehicle.fuel_level_percent}, } - self.vehicle_seen[vehicle.id] = \ - vehicle.updated_at or vehicle.created_at + self.vehicle_seen[vehicle.id] = vehicle.updated_at or vehicle.created_at if vehicle.latest_location is not None: location = vehicle.latest_location @@ -348,8 +357,7 @@ class AutomaticData: trips = [] try: # Get the most recent trip for this vehicle - trips = yield from self.session.get_trips( - vehicle=vehicle.id, limit=1) + trips = yield from self.session.get_trips(vehicle=vehicle.id, limit=1) except aioautomatic.exceptions.AutomaticError as err: _LOGGER.error(str(err)) diff --git a/homeassistant/components/device_tracker/bbox.py b/homeassistant/components/device_tracker/bbox.py index 6d870364d..b7818a005 100644 --- a/homeassistant/components/device_tracker/bbox.py +++ b/homeassistant/components/device_tracker/bbox.py @@ -12,7 +12,7 @@ import homeassistant.util.dt as dt_util from homeassistant.components.device_tracker import DOMAIN, DeviceScanner from homeassistant.util import Throttle -REQUIREMENTS = ['pybbox==0.0.5-alpha'] +REQUIREMENTS = ["pybbox==0.0.5-alpha"] _LOGGER = logging.getLogger(__name__) @@ -26,7 +26,7 @@ def get_scanner(hass, config): return scanner if scanner.success_init else None -Device = namedtuple('Device', ['mac', 'name', 'ip', 'last_update']) +Device = namedtuple("Device", ["mac", "name", "ip", "last_update"]) class BboxDeviceScanner(DeviceScanner): @@ -47,8 +47,9 @@ class BboxDeviceScanner(DeviceScanner): def get_device_name(self, device): """Return the name of the given device or None if we don't know.""" - filter_named = [result.name for result in self.last_results if - result.mac == device] + filter_named = [ + result.name for result in self.last_results if result.mac == device + ] if filter_named: return filter_named[0] @@ -70,11 +71,13 @@ class BboxDeviceScanner(DeviceScanner): now = dt_util.now() last_results = [] for device in result: - if device['active'] != 1: + if device["active"] != 1: continue last_results.append( - Device(device['macaddress'], device['hostname'], - device['ipaddress'], now)) + Device( + device["macaddress"], device["hostname"], device["ipaddress"], now + ) + ) self.last_results = last_results diff --git a/homeassistant/components/device_tracker/bluetooth_le_tracker.py b/homeassistant/components/device_tracker/bluetooth_le_tracker.py index 47b86ab9a..1ad5e5c6f 100644 --- a/homeassistant/components/device_tracker/bluetooth_le_tracker.py +++ b/homeassistant/components/device_tracker/bluetooth_le_tracker.py @@ -8,16 +8,20 @@ import logging 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, SOURCE_TYPE_BLUETOOTH_LE + YAML_DEVICES, + CONF_TRACK_NEW, + CONF_SCAN_INTERVAL, + DEFAULT_SCAN_INTERVAL, + load_config, + SOURCE_TYPE_BLUETOOTH_LE, ) import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pygatt==3.2.0'] +REQUIREMENTS = ["pygatt==3.2.0"] -BLE_PREFIX = 'BLE_' +BLE_PREFIX = "BLE_" MIN_SEEN_NEW = 5 @@ -25,14 +29,14 @@ def setup_scanner(hass, config, see, discovery_info=None): """Set up the Bluetooth LE Scanner.""" # pylint: disable=import-error import pygatt + new_devices = {} def see_device(address, name, new_device=False): """Mark a device as seen.""" if new_device: if address in new_devices: - _LOGGER.debug( - "Seen %s %s times", address, new_devices[address]) + _LOGGER.debug("Seen %s %s times", address, new_devices[address]) new_devices[address] += 1 if new_devices[address] >= MIN_SEEN_NEW: _LOGGER.debug("Adding %s to tracked devices", address) @@ -44,8 +48,11 @@ def setup_scanner(hass, config, see, discovery_info=None): new_devices[address] = 1 return - see(mac=BLE_PREFIX + address, host_name=name.strip("\x00"), - source_type=SOURCE_TYPE_BLUETOOTH_LE) + see( + mac=BLE_PREFIX + address, + host_name=name.strip("\x00"), + source_type=SOURCE_TYPE_BLUETOOTH_LE, + ) def discover_ble_devices(): """Discover Bluetooth LE devices.""" @@ -54,7 +61,7 @@ def setup_scanner(hass, config, see, discovery_info=None): adapter = pygatt.GATTToolBackend() devs = adapter.scan() - devices = {x['address']: x['name'] for x in devs} + 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) @@ -101,8 +108,7 @@ def setup_scanner(hass, config, see, discovery_info=None): if track_new: for address in devs: - if address not in devs_to_track and \ - address not in devs_donot_track: + if address not in devs_to_track and address not in devs_donot_track: _LOGGER.info("Discovered Bluetooth LE device %s", address) see_device(address, devs[address], new_device=True) diff --git a/homeassistant/components/device_tracker/bluetooth_tracker.py b/homeassistant/components/device_tracker/bluetooth_tracker.py index d22a1ba7c..dcf74f650 100644 --- a/homeassistant/components/device_tracker/bluetooth_tracker.py +++ b/homeassistant/components/device_tracker/bluetooth_tracker.py @@ -11,23 +11,32 @@ import voluptuous as vol 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, - DOMAIN) + YAML_DEVICES, + CONF_TRACK_NEW, + CONF_SCAN_INTERVAL, + DEFAULT_SCAN_INTERVAL, + load_config, + PLATFORM_SCHEMA, + DEFAULT_TRACK_NEW, + SOURCE_TYPE_BLUETOOTH, + DOMAIN, +) import homeassistant.util.dt as dt_util _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pybluez==0.22', 'bt_proximity==0.1.2'] +REQUIREMENTS = ["pybluez==0.22", "bt_proximity==0.1.2"] -BT_PREFIX = 'BT_' +BT_PREFIX = "BT_" -CONF_REQUEST_RSSI = 'request_rssi' +CONF_REQUEST_RSSI = "request_rssi" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_TRACK_NEW): cv.boolean, - vol.Optional(CONF_REQUEST_RSSI): cv.boolean -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_TRACK_NEW): cv.boolean, + vol.Optional(CONF_REQUEST_RSSI): cv.boolean, + } +) def setup_scanner(hass, config, see, discovery_info=None): @@ -40,15 +49,19 @@ def setup_scanner(hass, config, see, discovery_info=None): """Mark a device as seen.""" attributes = {} if rssi is not None: - attributes['rssi'] = rssi - see(mac="{}{}".format(BT_PREFIX, mac), host_name=name, - attributes=attributes, source_type=SOURCE_TYPE_BLUETOOTH) + attributes["rssi"] = rssi + see( + mac="{}{}".format(BT_PREFIX, mac), + host_name=name, + attributes=attributes, + source_type=SOURCE_TYPE_BLUETOOTH, + ) def discover_devices(): """Discover Bluetooth devices.""" result = bluetooth.discover_devices( - duration=8, lookup_names=True, flush_cache=True, - lookup_class=False) + duration=8, lookup_names=True, flush_cache=True, lookup_class=False + ) _LOGGER.debug("Bluetooth devices discovered = %d", len(result)) return result @@ -71,8 +84,7 @@ def setup_scanner(hass, config, see, discovery_info=None): track_new = config.get(CONF_TRACK_NEW, DEFAULT_TRACK_NEW) if track_new: for dev in discover_devices(): - if dev[0] not in devs_to_track and \ - dev[0] not in devs_donot_track: + if dev[0] not in devs_to_track and dev[0] not in devs_donot_track: devs_to_track.append(dev[0]) see_device(dev[0], dev[1]) @@ -83,16 +95,14 @@ def setup_scanner(hass, config, see, discovery_info=None): 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) + 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: for dev in discover_devices(): - if dev[0] not in devs_to_track and \ - dev[0] not in devs_donot_track: + if dev[0] not in devs_to_track and dev[0] not in devs_donot_track: devs_to_track.append(dev[0]) for mac in devs_to_track: _LOGGER.debug("Scanning %s", mac) @@ -113,7 +123,6 @@ def setup_scanner(hass, config, see, discovery_info=None): update_bluetooth(dt_util.utcnow()) - hass.services.register( - DOMAIN, "bluetooth_tracker_update", handle_update_bluetooth) + hass.services.register(DOMAIN, "bluetooth_tracker_update", handle_update_bluetooth) return True diff --git a/homeassistant/components/device_tracker/bmw_connected_drive.py b/homeassistant/components/device_tracker/bmw_connected_drive.py index 02a126531..b88170ee2 100644 --- a/homeassistant/components/device_tracker/bmw_connected_drive.py +++ b/homeassistant/components/device_tracker/bmw_connected_drive.py @@ -5,11 +5,10 @@ https://home-assistant.io/components/device_tracker.bmw_connected_drive/ """ import logging -from homeassistant.components.bmw_connected_drive import DOMAIN \ - as BMW_DOMAIN +from homeassistant.components.bmw_connected_drive import DOMAIN as BMW_DOMAIN from homeassistant.util import slugify -DEPENDENCIES = ['bmw_connected_drive'] +DEPENDENCIES = ["bmw_connected_drive"] _LOGGER = logging.getLogger(__name__) @@ -17,8 +16,7 @@ _LOGGER = logging.getLogger(__name__) def setup_scanner(hass, config, see, discovery_info=None): """Set up the BMW tracker.""" accounts = hass.data[BMW_DOMAIN] - _LOGGER.debug('Found BMW accounts: %s', - ', '.join([a.name for a in accounts])) + _LOGGER.debug("Found BMW accounts: %s", ", ".join([a.name for a in accounts])) for account in accounts: for vehicle in account.account.vehicles: tracker = BMWDeviceTracker(see, vehicle) @@ -44,15 +42,15 @@ class BMWDeviceTracker: dev_id = slugify(self.vehicle.name) if not self.vehicle.state.is_vehicle_tracking_enabled: - _LOGGER.debug('Tracking is disabled for vehicle %s', dev_id) + _LOGGER.debug("Tracking is disabled for vehicle %s", dev_id) return - _LOGGER.debug('Updating %s', dev_id) - attrs = { - 'vin': self.vehicle.vin, - } + _LOGGER.debug("Updating %s", dev_id) + attrs = {"vin": self.vehicle.vin} self._see( - dev_id=dev_id, host_name=self.vehicle.name, - gps=self.vehicle.state.gps_position, attributes=attrs, - icon='mdi:car' + dev_id=dev_id, + host_name=self.vehicle.name, + gps=self.vehicle.state.gps_position, + attributes=attrs, + icon="mdi:car", ) diff --git a/homeassistant/components/device_tracker/bt_home_hub_5.py b/homeassistant/components/device_tracker/bt_home_hub_5.py index 21c41df3a..7fda27ae0 100644 --- a/homeassistant/components/device_tracker/bt_home_hub_5.py +++ b/homeassistant/components/device_tracker/bt_home_hub_5.py @@ -9,19 +9,22 @@ import logging import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import (DOMAIN, PLATFORM_SCHEMA, - DeviceScanner) +from homeassistant.components.device_tracker import ( + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST -REQUIREMENTS = ['bthomehub5-devicelist==0.1.1'] +REQUIREMENTS = ["bthomehub5-devicelist==0.1.1"] _LOGGER = logging.getLogger(__name__) -CONF_DEFAULT_IP = '192.168.1.254' +CONF_DEFAULT_IP = "192.168.1.254" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string} +) def get_scanner(hass, config): diff --git a/homeassistant/components/device_tracker/cisco_ios.py b/homeassistant/components/device_tracker/cisco_ios.py index 1afea2c16..0103a2219 100644 --- a/homeassistant/components/device_tracker/cisco_ios.py +++ b/homeassistant/components/device_tracker/cisco_ios.py @@ -10,21 +10,25 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) -from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, \ - CONF_PORT + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_PORT _LOGGER = logging.getLogger(__name__) -REQUIREMENTS = ['pexpect==4.6.0'] +REQUIREMENTS = ["pexpect==4.6.0"] PLATFORM_SCHEMA = vol.All( - PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_PASSWORD, default=''): cv.string, - vol.Optional(CONF_PORT): cv.port, - }) + PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_PASSWORD, default=""): cv.string, + vol.Optional(CONF_PORT): cv.port, + } + ) ) @@ -48,7 +52,7 @@ class CiscoDeviceScanner(DeviceScanner): self.last_results = {} self.success_init = self._update_info() - _LOGGER.info('cisco_ios scanner initialized') + _LOGGER.info("cisco_ios scanner initialized") def get_device_name(self, device): """Get the firmware doesn't save the name of the wireless device.""" @@ -108,15 +112,20 @@ class CiscoDeviceScanner(DeviceScanner): try: cisco_ssh = pxssh.pxssh() - cisco_ssh.login(self.host, self.username, self.password, - port=self.port, auto_prompt_reset=False) + cisco_ssh.login( + self.host, + self.username, + self.password, + port=self.port, + auto_prompt_reset=False, + ) # Find the hostname - initial_line = cisco_ssh.before.decode('utf-8').splitlines() + initial_line = cisco_ssh.before.decode("utf-8").splitlines() router_hostname = initial_line[len(initial_line) - 1] router_hostname += "#" # Set the discovered hostname as prompt - regex_expression = ('(?i)^%s' % router_hostname).encode() + regex_expression = ("(?i)^%s" % router_hostname).encode() cisco_ssh.PROMPT = re.compile(regex_expression, re.MULTILINE) # Allow full arp table to print at once cisco_ssh.sendline("terminal length 0") @@ -127,7 +136,7 @@ class CiscoDeviceScanner(DeviceScanner): devices_result = cisco_ssh.before - return devices_result.decode('utf-8') + return devices_result.decode("utf-8") except pxssh.ExceptionPxssh as px_e: _LOGGER.error("pxssh failed on login") _LOGGER.error(px_e) @@ -148,8 +157,9 @@ def _parse_cisco_mac_address(cisco_hardware_addr): Takes in cisco_hwaddr: HWAddr String from Cisco ARP table Returns a regular standard MAC address """ - cisco_hardware_addr = cisco_hardware_addr.replace('.', '') - blocks = [cisco_hardware_addr[x:x + 2] - for x in range(0, len(cisco_hardware_addr), 2)] + cisco_hardware_addr = cisco_hardware_addr.replace(".", "") + blocks = [ + cisco_hardware_addr[x : x + 2] for x in range(0, len(cisco_hardware_addr), 2) + ] - return ':'.join(blocks).upper() + return ":".join(blocks).upper() diff --git a/homeassistant/components/device_tracker/ddwrt.py b/homeassistant/components/device_tracker/ddwrt.py index 539d4fde5..1e3af42bb 100644 --- a/homeassistant/components/device_tracker/ddwrt.py +++ b/homeassistant/components/device_tracker/ddwrt.py @@ -12,19 +12,24 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME _LOGGER = logging.getLogger(__name__) -_DDWRT_DATA_REGEX = re.compile(r'\{(\w+)::([^\}]*)\}') -_MAC_REGEX = re.compile(r'(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})') +_DDWRT_DATA_REGEX = re.compile(r"\{(\w+)::([^\}]*)\}") +_MAC_REGEX = re.compile(r"(([0-9A-Fa-f]{1,2}\:){5}[0-9A-Fa-f]{1,2})") -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + } +) def get_scanner(hass, config): @@ -48,10 +53,10 @@ class DdWrtDeviceScanner(DeviceScanner): self.mac2name = {} # Test the router is accessible - url = 'http://{}/Status_Wireless.live.asp'.format(self.host) + url = "http://{}/Status_Wireless.live.asp".format(self.host) data = self.get_ddwrt_data(url) if not data: - raise ConnectionError('Cannot connect to DD-Wrt router') + raise ConnectionError("Cannot connect to DD-Wrt router") def scan_devices(self): """Scan for new devices and return a list with found device IDs.""" @@ -63,21 +68,20 @@ class DdWrtDeviceScanner(DeviceScanner): """Return the name of the given device or None if we don't know.""" # If not initialised and not already scanned and not found. if device not in self.mac2name: - url = 'http://{}/Status_Lan.live.asp'.format(self.host) + url = "http://{}/Status_Lan.live.asp".format(self.host) data = self.get_ddwrt_data(url) if not data: return None - dhcp_leases = data.get('dhcp_leases', None) + dhcp_leases = data.get("dhcp_leases", None) if not dhcp_leases: return None # Remove leading and trailing quotes and spaces - cleaned_str = dhcp_leases.replace( - "\"", "").replace("\'", "").replace(" ", "") - elements = cleaned_str.split(',') + cleaned_str = dhcp_leases.replace('"', "").replace("'", "").replace(" ", "") + elements = cleaned_str.split(",") num_clients = int(len(elements) / 5) self.mac2name = {} for idx in range(0, num_clients): @@ -98,7 +102,7 @@ class DdWrtDeviceScanner(DeviceScanner): """ _LOGGER.info("Checking ARP") - url = 'http://{}/Status_Wireless.live.asp'.format(self.host) + url = "http://{}/Status_Wireless.live.asp".format(self.host) data = self.get_ddwrt_data(url) if not data: @@ -106,7 +110,7 @@ class DdWrtDeviceScanner(DeviceScanner): self.last_results = [] - active_clients = data.get('active_wireless', None) + active_clients = data.get("active_wireless", None) if not active_clients: return False @@ -116,16 +120,14 @@ class DdWrtDeviceScanner(DeviceScanner): clean_str = active_clients.strip().strip("'") elements = clean_str.split("','") - self.last_results.extend(item for item in elements - if _MAC_REGEX.match(item)) + self.last_results.extend(item for item in elements if _MAC_REGEX.match(item)) return True def get_ddwrt_data(self, url): """Retrieve data from DD-WRT and return parsed result.""" try: - response = requests.get( - url, auth=(self.username, self.password), timeout=4) + response = requests.get(url, auth=(self.username, self.password), timeout=4) except requests.exceptions.Timeout: _LOGGER.exception("Connection to the router timed out") return @@ -134,13 +136,12 @@ class DdWrtDeviceScanner(DeviceScanner): if response.status_code == 401: # Authentication error _LOGGER.exception( - "Failed to authenticate, check your username and password") + "Failed to authenticate, check your username and password" + ) return _LOGGER.error("Invalid response from DD-WRT: %s", response) def _parse_ddwrt_response(data_str): """Parse the DD-WRT data format.""" - return { - key: val for key, val in _DDWRT_DATA_REGEX - .findall(data_str)} + return {key: val for key, val in _DDWRT_DATA_REGEX.findall(data_str)} diff --git a/homeassistant/components/device_tracker/demo.py b/homeassistant/components/device_tracker/demo.py index 608fc560c..6b24469e8 100644 --- a/homeassistant/components/device_tracker/demo.py +++ b/homeassistant/components/device_tracker/demo.py @@ -11,6 +11,7 @@ from homeassistant.components.device_tracker import DOMAIN def setup_scanner(hass, config, see, discovery_info=None): """Set up the demo tracker.""" + def offset(): """Return random offset.""" return (random.randrange(500, 2000)) / 2e5 * random.choice((-1, 1)) @@ -20,27 +21,26 @@ def setup_scanner(hass, config, see, discovery_info=None): see( dev_id=dev_id, host_name=name, - gps=(hass.config.latitude + offset(), - hass.config.longitude + offset()), + gps=(hass.config.latitude + offset(), hass.config.longitude + offset()), gps_accuracy=random.randrange(50, 150), - battery=random.randrange(10, 90) + battery=random.randrange(10, 90), ) def observe(call=None): """Observe three entities.""" - random_see('demo_paulus', 'Paulus') - random_see('demo_anne_therese', 'Anne Therese') + random_see("demo_paulus", "Paulus") + random_see("demo_anne_therese", "Anne Therese") observe() see( - dev_id='demo_home_boy', - host_name='Home Boy', + dev_id="demo_home_boy", + host_name="Home Boy", gps=[hass.config.latitude - 0.00002, hass.config.longitude + 0.00002], gps_accuracy=20, - battery=53 + battery=53, ) - hass.services.register(DOMAIN, 'demo', observe) + hass.services.register(DOMAIN, "demo", observe) return True diff --git a/homeassistant/components/device_tracker/freebox.py b/homeassistant/components/device_tracker/freebox.py index 2cac81fd4..3bde94981 100644 --- a/homeassistant/components/device_tracker/freebox.py +++ b/homeassistant/components/device_tracker/freebox.py @@ -18,21 +18,23 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_time_interval from homeassistant.components.device_tracker import ( - PLATFORM_SCHEMA, CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) -from homeassistant.const import ( - CONF_HOST, CONF_PORT) + PLATFORM_SCHEMA, + CONF_SCAN_INTERVAL, + DEFAULT_SCAN_INTERVAL, +) +from homeassistant.const import CONF_HOST, CONF_PORT -REQUIREMENTS = ['aiofreepybox==0.0.4'] +REQUIREMENTS = ["aiofreepybox==0.0.4"] _LOGGER = logging.getLogger(__name__) -FREEBOX_CONFIG_FILE = 'freebox.conf' +FREEBOX_CONFIG_FILE = "freebox.conf" PLATFORM_SCHEMA = vol.All( - PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PORT): cv.port - })) + PLATFORM_SCHEMA.extend( + {vol.Required(CONF_HOST): cv.string, vol.Required(CONF_PORT): cv.port} + ) +) MIN_TIME_BETWEEN_SCANS = timedelta(seconds=10) @@ -41,10 +43,13 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None): """Set up the Freebox device tracker and start the polling.""" freebox_config = copy.deepcopy(config) if discovery_info is not None: - freebox_config[CONF_HOST] = discovery_info['properties']['api_domain'] - freebox_config[CONF_PORT] = discovery_info['properties']['https_port'] - _LOGGER.info("Discovered Freebox server: %s:%s", - freebox_config[CONF_HOST], freebox_config[CONF_PORT]) + freebox_config[CONF_HOST] = discovery_info["properties"]["api_domain"] + freebox_config[CONF_PORT] = discovery_info["properties"]["https_port"] + _LOGGER.info( + "Discovered Freebox server: %s:%s", + freebox_config[CONF_HOST], + freebox_config[CONF_PORT], + ) scanner = FreeboxDeviceScanner(hass, freebox_config, async_see) interval = freebox_config.get(CONF_SCAN_INTERVAL, DEFAULT_SCAN_INTERVAL) @@ -52,14 +57,15 @@ async def async_setup_scanner(hass, config, async_see, discovery_info=None): return True -Device = namedtuple('Device', ['id', 'name', 'ip']) +Device = namedtuple("Device", ["id", "name", "ip"]) def _build_device(device_dict): return Device( - device_dict['l2ident']['id'], - device_dict['primary_name'], - device_dict['l3connectivities'][0]['addr']) + device_dict["l2ident"]["id"], + device_dict["primary_name"], + device_dict["l3connectivities"][0]["addr"], + ) class FreeboxDeviceScanner: @@ -79,17 +85,16 @@ class FreeboxDeviceScanner: # The version can be changed if we want the user to re-authorize HASS # on her Freebox. app_desc = { - 'app_id': 'hass', - 'app_name': 'Home Assistant', - 'app_version': '0.65', - 'device_name': socket.gethostname() + "app_id": "hass", + "app_name": "Home Assistant", + "app_version": "0.65", + "device_name": socket.gethostname(), } - api_version = 'v1' # Use the lowest working version. + api_version = "v1" # Use the lowest working version. self.fbx = Freepybox( - app_desc=app_desc, - token_file=self.token_file, - api_version=api_version) + app_desc=app_desc, token_file=self.token_file, api_version=api_version + ) async def async_start(self, hass, interval): """Perform a first update and start polling at the given interval.""" @@ -101,20 +106,21 @@ class FreeboxDeviceScanner: """Check the Freebox for devices.""" from aiofreepybox.exceptions import HttpRequestError - _LOGGER.info('Scanning devices') + _LOGGER.info("Scanning devices") await self.fbx.open(self.host, self.port) try: hosts = await self.fbx.lan.get_hosts_list() except HttpRequestError: - _LOGGER.exception('Failed to scan devices') + _LOGGER.exception("Failed to scan devices") else: - active_devices = [_build_device(device) - for device in hosts - if device['active']] + active_devices = [ + _build_device(device) for device in hosts if device["active"] + ] if active_devices: - await asyncio.wait([self.async_see(mac=d.id, host_name=d.name) - for d in active_devices]) + await asyncio.wait( + [self.async_see(mac=d.id, host_name=d.name) for d in active_devices] + ) await self.fbx.close() diff --git a/homeassistant/components/device_tracker/fritz.py b/homeassistant/components/device_tracker/fritz.py index 8c9d1988a..38d271beb 100644 --- a/homeassistant/components/device_tracker/fritz.py +++ b/homeassistant/components/device_tracker/fritz.py @@ -10,20 +10,25 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME -REQUIREMENTS = ['fritzconnection==0.6.5'] +REQUIREMENTS = ["fritzconnection==0.6.5"] _LOGGER = logging.getLogger(__name__) -CONF_DEFAULT_IP = '169.254.1.1' # This IP is valid for all FRITZ!Box routers. +CONF_DEFAULT_IP = "169.254.1.1" # This IP is valid for all FRITZ!Box routers. -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, - vol.Optional(CONF_PASSWORD, default='admin'): cv.string, - vol.Optional(CONF_USERNAME, default=''): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Optional(CONF_HOST, default=CONF_DEFAULT_IP): cv.string, + vol.Optional(CONF_PASSWORD, default="admin"): cv.string, + vol.Optional(CONF_USERNAME, default=""): cv.string, + } +) def get_scanner(hass, config): @@ -49,7 +54,8 @@ class FritzBoxScanner(DeviceScanner): # Establish a connection to the FRITZ!Box. try: self.fritz_box = fc.FritzHosts( - address=self.host, user=self.username, password=self.password) + address=self.host, user=self.username, password=self.password + ) except (ValueError, TypeError): self.fritz_box = None @@ -59,27 +65,25 @@ class FritzBoxScanner(DeviceScanner): self.success_init = False if self.success_init: - _LOGGER.info("Successfully connected to %s", - self.fritz_box.modelname) + _LOGGER.info("Successfully connected to %s", self.fritz_box.modelname) self._update_info() else: - _LOGGER.error("Failed to establish connection to FRITZ!Box " - "with IP: %s", self.host) + _LOGGER.error( + "Failed to establish connection to FRITZ!Box " "with IP: %s", self.host + ) def scan_devices(self): """Scan for new devices and return a list of found device ids.""" self._update_info() active_hosts = [] for known_host in self.last_results: - if known_host['status'] == '1' and known_host.get('mac'): - active_hosts.append(known_host['mac']) + if known_host["status"] == "1" and known_host.get("mac"): + active_hosts.append(known_host["mac"]) return active_hosts def get_device_name(self, device): """Return the name of the given device or None if is not known.""" - ret = self.fritz_box.get_specific_host_entry(device).get( - 'NewHostName' - ) + ret = self.fritz_box.get_specific_host_entry(device).get("NewHostName") if ret == {}: return None return ret diff --git a/homeassistant/components/device_tracker/geofency.py b/homeassistant/components/device_tracker/geofency.py index 7231c5127..6dcfffc6b 100644 --- a/homeassistant/components/device_tracker/geofency.py +++ b/homeassistant/components/device_tracker/geofency.py @@ -13,29 +13,32 @@ import voluptuous as vol from homeassistant.components.device_tracker import PLATFORM_SCHEMA from homeassistant.components.http import HomeAssistantView from homeassistant.const import ( - ATTR_LATITUDE, ATTR_LONGITUDE, HTTP_UNPROCESSABLE_ENTITY, STATE_NOT_HOME) + ATTR_LATITUDE, + ATTR_LONGITUDE, + HTTP_UNPROCESSABLE_ENTITY, + STATE_NOT_HOME, +) import homeassistant.helpers.config_validation as cv from homeassistant.util import slugify _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['http'] +DEPENDENCIES = ["http"] -ATTR_CURRENT_LATITUDE = 'currentLatitude' -ATTR_CURRENT_LONGITUDE = 'currentLongitude' +ATTR_CURRENT_LATITUDE = "currentLatitude" +ATTR_CURRENT_LONGITUDE = "currentLongitude" -BEACON_DEV_PREFIX = 'beacon' -CONF_MOBILE_BEACONS = 'mobile_beacons' +BEACON_DEV_PREFIX = "beacon" +CONF_MOBILE_BEACONS = "mobile_beacons" -LOCATION_ENTRY = '1' -LOCATION_EXIT = '0' +LOCATION_ENTRY = "1" +LOCATION_EXIT = "0" -URL = '/api/geofency' +URL = "/api/geofency" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_MOBILE_BEACONS): vol.All( - cv.ensure_list, [cv.string]), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + {vol.Optional(CONF_MOBILE_BEACONS): vol.All(cv.ensure_list, [cv.string])} +) def setup_scanner(hass, config, see, discovery_info=None): @@ -51,7 +54,7 @@ class GeofencyView(HomeAssistantView): """View to handle Geofency requests.""" url = URL - name = 'api:geofency' + name = "api:geofency" def __init__(self, see, mobile_beacons): """Initialize Geofency url endpoints.""" @@ -62,7 +65,7 @@ class GeofencyView(HomeAssistantView): def post(self, request): """Handle Geofency requests.""" data = yield from request.post() - hass = request.app['hass'] + hass = request.app["hass"] data = self._validate_data(data) if not data: @@ -70,8 +73,8 @@ class GeofencyView(HomeAssistantView): if self._is_mobile_beacon(data): return (yield from self._set_location(hass, data, None)) - if data['entry'] == LOCATION_ENTRY: - location_name = data['name'] + if data["entry"] == LOCATION_ENTRY: + location_name = data["name"] else: location_name = STATE_NOT_HOME if ATTR_CURRENT_LATITUDE in data: @@ -85,8 +88,14 @@ class GeofencyView(HomeAssistantView): """Validate POST payload.""" data = data.copy() - required_attributes = ['address', 'device', 'entry', - 'latitude', 'longitude', 'name'] + required_attributes = [ + "address", + "device", + "entry", + "latitude", + "longitude", + "name", + ] valid = True for attribute in required_attributes: @@ -97,12 +106,16 @@ class GeofencyView(HomeAssistantView): if not valid: return False - data['address'] = data['address'].replace('\n', ' ') - data['device'] = slugify(data['device']) - data['name'] = slugify(data['name']) + data["address"] = data["address"].replace("\n", " ") + data["device"] = slugify(data["device"]) + data["name"] = slugify(data["name"]) - gps_attributes = [ATTR_LATITUDE, ATTR_LONGITUDE, - ATTR_CURRENT_LATITUDE, ATTR_CURRENT_LONGITUDE] + gps_attributes = [ + ATTR_LATITUDE, + ATTR_LONGITUDE, + ATTR_CURRENT_LATITUDE, + ATTR_CURRENT_LONGITUDE, + ] for attribute in gps_attributes: if attribute in data: @@ -112,14 +125,14 @@ class GeofencyView(HomeAssistantView): def _is_mobile_beacon(self, data): """Check if we have a mobile beacon.""" - return 'beaconUUID' in data and data['name'] in self.mobile_beacons + return "beaconUUID" in data and data["name"] in self.mobile_beacons @staticmethod def _device_name(data): """Return name of device tracker.""" - if 'beaconUUID' in data: - return "{}_{}".format(BEACON_DEV_PREFIX, data['name']) - return data['device'] + if "beaconUUID" in data: + return "{}_{}".format(BEACON_DEV_PREFIX, data["name"]) + return data["device"] @asyncio.coroutine def _set_location(self, hass, data, location_name): @@ -127,9 +140,13 @@ class GeofencyView(HomeAssistantView): device = self._device_name(data) yield from hass.async_add_job( - partial(self.see, dev_id=device, - gps=(data[ATTR_LATITUDE], data[ATTR_LONGITUDE]), - location_name=location_name, - attributes=data)) + partial( + self.see, + dev_id=device, + gps=(data[ATTR_LATITUDE], data[ATTR_LONGITUDE]), + location_name=location_name, + attributes=data, + ) + ) return "Setting location for {}".format(device) diff --git a/homeassistant/components/device_tracker/google_maps.py b/homeassistant/components/device_tracker/google_maps.py index 170d3de68..f15bcbba9 100644 --- a/homeassistant/components/device_tracker/google_maps.py +++ b/homeassistant/components/device_tracker/google_maps.py @@ -9,34 +9,35 @@ import logging import voluptuous as vol -from homeassistant.components.device_tracker import ( - PLATFORM_SCHEMA, SOURCE_TYPE_GPS) +from homeassistant.components.device_tracker import PLATFORM_SCHEMA, SOURCE_TYPE_GPS 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, dt as dt_util -REQUIREMENTS = ['locationsharinglib==2.0.11'] +REQUIREMENTS = ["locationsharinglib==2.0.11"] _LOGGER = logging.getLogger(__name__) -ATTR_ADDRESS = 'address' -ATTR_FULL_NAME = 'full_name' -ATTR_LAST_SEEN = 'last_seen' -ATTR_NICKNAME = 'nickname' +ATTR_ADDRESS = "address" +ATTR_FULL_NAME = "full_name" +ATTR_LAST_SEEN = "last_seen" +ATTR_NICKNAME = "nickname" -CONF_MAX_GPS_ACCURACY = 'max_gps_accuracy' +CONF_MAX_GPS_ACCURACY = "max_gps_accuracy" -CREDENTIALS_FILE = '.google_maps_location_sharing.cookies' +CREDENTIALS_FILE = ".google_maps_location_sharing.cookies" MIN_TIME_BETWEEN_SCANS = timedelta(seconds=30) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Optional(CONF_MAX_GPS_ACCURACY, default=100000): vol.Coerce(float), -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Optional(CONF_MAX_GPS_ACCURACY, default=100000): vol.Coerce(float), + } +) def setup_scanner(hass, config: ConfigType, see, discovery_info=None): @@ -59,12 +60,12 @@ class GoogleMapsScanner: self.max_gps_accuracy = config[CONF_MAX_GPS_ACCURACY] try: - self.service = Service(self.username, self.password, - hass.config.path(CREDENTIALS_FILE)) + self.service = Service( + self.username, self.password, hass.config.path(CREDENTIALS_FILE) + ) self._update_info() - track_time_interval( - hass, self._update_info, MIN_TIME_BETWEEN_SCANS) + track_time_interval(hass, self._update_info, MIN_TIME_BETWEEN_SCANS) self.success_init = True @@ -75,17 +76,22 @@ class GoogleMapsScanner: def _update_info(self, now=None): for person in self.service.get_all_people(): try: - dev_id = 'google_maps_{0}'.format(slugify(person.id)) + dev_id = "google_maps_{0}".format(slugify(person.id)) except TypeError: _LOGGER.warning("No location(s) shared with this account") return - if self.max_gps_accuracy is not None and \ - person.accuracy > self.max_gps_accuracy: - _LOGGER.info("Ignoring %s update because expected GPS " - "accuracy %s is not met: %s", - person.nickname, self.max_gps_accuracy, - person.accuracy) + if ( + self.max_gps_accuracy is not None + and person.accuracy > self.max_gps_accuracy + ): + _LOGGER.info( + "Ignoring %s update because expected GPS " + "accuracy %s is not met: %s", + person.nickname, + self.max_gps_accuracy, + person.accuracy, + ) continue attrs = { diff --git a/homeassistant/components/device_tracker/gpslogger.py b/homeassistant/components/device_tracker/gpslogger.py index 6336ba51d..67ead97b0 100644 --- a/homeassistant/components/device_tracker/gpslogger.py +++ b/homeassistant/components/device_tracker/gpslogger.py @@ -11,29 +11,23 @@ from aiohttp.web import Request, HTTPUnauthorized import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.const import ( - CONF_PASSWORD, HTTP_UNPROCESSABLE_ENTITY -) -from homeassistant.components.http import ( - CONF_API_PASSWORD, HomeAssistantView -) +from homeassistant.const import CONF_PASSWORD, HTTP_UNPROCESSABLE_ENTITY +from homeassistant.components.http import CONF_API_PASSWORD, HomeAssistantView + # pylint: disable=unused-import -from homeassistant.components.device_tracker import ( # NOQA - DOMAIN, PLATFORM_SCHEMA -) +from homeassistant.components.device_tracker import DOMAIN, PLATFORM_SCHEMA # NOQA from homeassistant.helpers.typing import HomeAssistantType, ConfigType _LOGGER = logging.getLogger(__name__) -DEPENDENCIES = ['http'] +DEPENDENCIES = ["http"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_PASSWORD): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Optional(CONF_PASSWORD): cv.string}) -async def async_setup_scanner(hass: HomeAssistantType, config: ConfigType, - async_see, discovery_info=None): +async def async_setup_scanner( + hass: HomeAssistantType, config: ConfigType, async_see, discovery_info=None +): """Set up an endpoint for the GPSLogger application.""" hass.http.register_view(GPSLoggerView(async_see, config)) @@ -43,8 +37,8 @@ async def async_setup_scanner(hass: HomeAssistantType, config: ConfigType, class GPSLoggerView(HomeAssistantView): """View to handle GPSLogger requests.""" - url = '/api/gpslogger' - name = 'api:gpslogger' + url = "/api/gpslogger" + name = "api:gpslogger" def __init__(self, async_see, config): """Initialize GPSLogger url endpoints.""" @@ -56,53 +50,53 @@ class GPSLoggerView(HomeAssistantView): async def get(self, request: Request): """Handle for GPSLogger message received as GET.""" - hass = request.app['hass'] + hass = request.app["hass"] data = request.query if self._password is not None: authenticated = CONF_API_PASSWORD in data and compare_digest( - self._password, - data[CONF_API_PASSWORD] + self._password, data[CONF_API_PASSWORD] ) if not authenticated: raise HTTPUnauthorized() - if 'latitude' not in data or 'longitude' not in data: - return ('Latitude and longitude not specified.', - HTTP_UNPROCESSABLE_ENTITY) + if "latitude" not in data or "longitude" not in data: + return ("Latitude and longitude not specified.", HTTP_UNPROCESSABLE_ENTITY) - if 'device' not in data: + if "device" not in data: _LOGGER.error("Device id not specified") - return ('Device id not specified.', - HTTP_UNPROCESSABLE_ENTITY) + return ("Device id not specified.", HTTP_UNPROCESSABLE_ENTITY) - device = data['device'].replace('-', '') - gps_location = (data['latitude'], data['longitude']) + device = data["device"].replace("-", "") + gps_location = (data["latitude"], data["longitude"]) accuracy = 200 battery = -1 - if 'accuracy' in data: - accuracy = int(float(data['accuracy'])) - if 'battery' in data: - battery = float(data['battery']) + if "accuracy" in data: + accuracy = int(float(data["accuracy"])) + if "battery" in data: + battery = float(data["battery"]) attrs = {} - if 'speed' in data: - attrs['speed'] = float(data['speed']) - if 'direction' in data: - attrs['direction'] = float(data['direction']) - if 'altitude' in data: - attrs['altitude'] = float(data['altitude']) - if 'provider' in data: - attrs['provider'] = data['provider'] - if 'activity' in data: - attrs['activity'] = data['activity'] + if "speed" in data: + attrs["speed"] = float(data["speed"]) + if "direction" in data: + attrs["direction"] = float(data["direction"]) + if "altitude" in data: + attrs["altitude"] = float(data["altitude"]) + if "provider" in data: + attrs["provider"] = data["provider"] + if "activity" in data: + attrs["activity"] = data["activity"] - hass.async_add_job(self.async_see( - dev_id=device, - gps=gps_location, battery=battery, - gps_accuracy=accuracy, - attributes=attrs - )) + hass.async_add_job( + self.async_see( + dev_id=device, + gps=gps_location, + battery=battery, + gps_accuracy=accuracy, + attributes=attrs, + ) + ) - return 'Setting location for {}'.format(device) + return "Setting location for {}".format(device) diff --git a/homeassistant/components/device_tracker/hitron_coda.py b/homeassistant/components/device_tracker/hitron_coda.py index 72817ca69..71be9e59a 100644 --- a/homeassistant/components/device_tracker/hitron_coda.py +++ b/homeassistant/components/device_tracker/hitron_coda.py @@ -12,21 +12,24 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) -from homeassistant.const import ( - CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_TYPE + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, ) +from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, CONF_TYPE _LOGGER = logging.getLogger(__name__) DEFAULT_TYPE = "rogers" -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_USERNAME): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Optional(CONF_TYPE, default=DEFAULT_TYPE): cv.string, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_TYPE, default=DEFAULT_TYPE): cv.string, + } +) def get_scanner(_hass, config): @@ -36,7 +39,7 @@ def get_scanner(_hass, config): return scanner if scanner.success_init else None -Device = namedtuple('Device', ['mac', 'name']) +Device = namedtuple("Device", ["mac", "name"]) class HitronCODADeviceScanner(DeviceScanner): @@ -46,16 +49,16 @@ class HitronCODADeviceScanner(DeviceScanner): """Initialize the scanner.""" self.last_results = [] host = config[CONF_HOST] - self._url = 'http://{}/data/getConnectInfo.asp'.format(host) - self._loginurl = 'http://{}/goform/login'.format(host) + self._url = "http://{}/data/getConnectInfo.asp".format(host) + self._loginurl = "http://{}/goform/login".format(host) self._username = config.get(CONF_USERNAME) self._password = config.get(CONF_PASSWORD) if config.get(CONF_TYPE) == "shaw": - self._type = 'pwd' + self._type = "pwd" else: - self._type = 'pws' + self._type = "pws" self._userid = None @@ -70,9 +73,9 @@ class HitronCODADeviceScanner(DeviceScanner): def get_device_name(self, device): """Return the name of the device with the given MAC address.""" - name = next(( - result.name for result in self.last_results - if result.mac == device), None) + name = next( + (result.name for result in self.last_results if result.mac == device), None + ) return name def _login(self): @@ -80,21 +83,16 @@ class HitronCODADeviceScanner(DeviceScanner): _LOGGER.info("Logging in to CODA...") try: - data = [ - ('user', self._username), - (self._type, self._password), - ] + data = [("user", self._username), (self._type, self._password)] res = requests.post(self._loginurl, data=data, timeout=10) except requests.exceptions.Timeout: - _LOGGER.error( - "Connection to the router timed out at URL %s", self._url) + _LOGGER.error("Connection to the router timed out at URL %s", self._url) return False if res.status_code != 200: - _LOGGER.error( - "Connection failed with http code %s", res.status_code) + _LOGGER.error("Connection failed with http code %s", res.status_code) return False try: - self._userid = res.cookies['userid'] + self._userid = res.cookies["userid"] return True except KeyError: _LOGGER.error("Failed to log in to router") @@ -112,16 +110,12 @@ class HitronCODADeviceScanner(DeviceScanner): # doing a request try: - res = requests.get(self._url, timeout=10, cookies={ - 'userid': self._userid - }) + res = requests.get(self._url, timeout=10, cookies={"userid": self._userid}) except requests.exceptions.Timeout: - _LOGGER.error( - "Connection to the router timed out at URL %s", self._url) + _LOGGER.error("Connection to the router timed out at URL %s", self._url) return False if res.status_code != 200: - _LOGGER.error( - "Connection failed with http code %s", res.status_code) + _LOGGER.error("Connection failed with http code %s", res.status_code) return False try: result = res.json() @@ -132,8 +126,8 @@ class HitronCODADeviceScanner(DeviceScanner): # parsing response for info in result: - mac = info['macAddr'] - name = info['hostName'] + mac = info["macAddr"] + name = info["hostName"] # No address = no item :) if mac is None: continue diff --git a/homeassistant/components/device_tracker/huawei_lte.py b/homeassistant/components/device_tracker/huawei_lte.py index 4b4eb3f00..0ee451053 100644 --- a/homeassistant/components/device_tracker/huawei_lte.py +++ b/homeassistant/components/device_tracker/huawei_lte.py @@ -10,18 +10,14 @@ import attr import voluptuous as vol import homeassistant.helpers.config_validation as cv -from homeassistant.components.device_tracker import ( - PLATFORM_SCHEMA, DeviceScanner, -) +from homeassistant.components.device_tracker import PLATFORM_SCHEMA, DeviceScanner from homeassistant.const import CONF_URL from ..huawei_lte import DATA_KEY, RouterData -DEPENDENCIES = ['huawei_lte'] +DEPENDENCIES = ["huawei_lte"] -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Optional(CONF_URL): cv.url, -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({vol.Optional(CONF_URL): cv.url}) def get_scanner(hass, config): diff --git a/homeassistant/components/device_tracker/huawei_router.py b/homeassistant/components/device_tracker/huawei_router.py index f5e4fa8a7..b2b77aeaa 100644 --- a/homeassistant/components/device_tracker/huawei_router.py +++ b/homeassistant/components/device_tracker/huawei_router.py @@ -14,16 +14,21 @@ import voluptuous as vol import homeassistant.helpers.config_validation as cv from homeassistant.components.device_tracker import ( - DOMAIN, PLATFORM_SCHEMA, DeviceScanner) + DOMAIN, + PLATFORM_SCHEMA, + DeviceScanner, +) from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME _LOGGER = logging.getLogger(__name__) -PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend({ - vol.Required(CONF_HOST): cv.string, - vol.Required(CONF_PASSWORD): cv.string, - vol.Required(CONF_USERNAME): cv.string -}) +PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend( + { + vol.Required(CONF_HOST): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Required(CONF_USERNAME): cv.string, + } +) def get_scanner(hass, config): @@ -33,14 +38,14 @@ def get_scanner(hass, config): return scanner -Device = namedtuple('Device', ['name', 'ip', 'mac', 'state']) +Device = namedtuple("Device", ["name", "ip", "mac", "state"]) class HuaweiDeviceScanner(DeviceScanner): """This class queries a router running HUAWEI firmware.""" - ARRAY_REGEX = re.compile(r'var UserDevinfo = new Array\((.*),null\);') - DEVICE_REGEX = re.compile(r'new USERDevice\((.*?)\),') + ARRAY_REGEX = re.compile(r"var UserDevinfo = new Array\((.*),null\);") + DEVICE_REGEX = re.compile(r"new USERDevice\((.*?)\),") DEVICE_ATTR_REGEX = re.compile( '"(?P.*?)","(?P.*?)",' '"(?P.*?)","(?P.*?)",' @@ -48,14 +53,15 @@ class HuaweiDeviceScanner(DeviceScanner): '"(?P.*?)","(?P.*?)",' '"(?P