diff --git a/homeassistant/components/system_bridge/__init__.py b/homeassistant/components/system_bridge/__init__.py index c6e64cbd4fb5dc..54ad486c223c7c 100644 --- a/homeassistant/components/system_bridge/__init__.py +++ b/homeassistant/components/system_bridge/__init__.py @@ -42,7 +42,6 @@ from homeassistant.exceptions import ( ConfigEntryAuthFailed, ConfigEntryNotReady, - HomeAssistantError, ServiceValidationError, ) from homeassistant.helpers import ( @@ -52,6 +51,7 @@ ) from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue +from homeassistant.helpers.typing import ConfigType from .config_flow import SystemBridgeConfigFlow from .const import DATA_WAIT_TIMEOUT, DOMAIN, MODULES @@ -105,137 +105,11 @@ def _get_coordinator( "sleep": "power_sleep", } +CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN) -async def async_setup_entry( - hass: HomeAssistant, - entry: SystemBridgeConfigEntry, -) -> bool: - """Set up System Bridge from a config entry.""" - - # Check version before initialising - version = Version( - entry.data[CONF_HOST], - entry.data[CONF_PORT], - entry.data[CONF_TOKEN], - session=async_get_clientsession(hass), - ) - supported = False - try: - async with asyncio.timeout(DATA_WAIT_TIMEOUT): - supported = await version.check_supported() - except AuthenticationException as exception: - _LOGGER.error("Authentication failed for %s: %s", entry.title, exception) - raise ConfigEntryAuthFailed( - translation_domain=DOMAIN, - translation_key="authentication_failed", - translation_placeholders={ - "title": entry.title, - "host": entry.data[CONF_HOST], - }, - ) from exception - except (ConnectionClosedException, ConnectionErrorException) as exception: - raise ConfigEntryNotReady( - translation_domain=DOMAIN, - translation_key="connection_failed", - translation_placeholders={ - "title": entry.title, - "host": entry.data[CONF_HOST], - }, - ) from exception - except TimeoutError as exception: - raise ConfigEntryNotReady( - translation_domain=DOMAIN, - translation_key="timeout", - translation_placeholders={ - "title": entry.title, - "host": entry.data[CONF_HOST], - }, - ) from exception - - # If not supported, create an issue and raise ConfigEntryNotReady - if not supported: - async_create_issue( - hass=hass, - domain=DOMAIN, - issue_id=f"system_bridge_{entry.entry_id}_unsupported_version", - translation_key="unsupported_version", - translation_placeholders={"host": entry.data[CONF_HOST]}, - severity=IssueSeverity.ERROR, - is_fixable=False, - ) - raise ConfigEntryNotReady( - translation_domain=DOMAIN, - translation_key="unsupported_version", - translation_placeholders={ - "title": entry.title, - "host": entry.data[CONF_HOST], - }, - ) - coordinator = SystemBridgeDataUpdateCoordinator( - hass, - _LOGGER, - entry=entry, - ) - - try: - async with asyncio.timeout(DATA_WAIT_TIMEOUT): - await coordinator.async_get_data(MODULES) - except AuthenticationException as exception: - _LOGGER.error("Authentication failed for %s: %s", entry.title, exception) - raise ConfigEntryAuthFailed( - translation_domain=DOMAIN, - translation_key="authentication_failed", - translation_placeholders={ - "title": entry.title, - "host": entry.data[CONF_HOST], - }, - ) from exception - except (ConnectionClosedException, ConnectionErrorException) as exception: - raise ConfigEntryNotReady( - translation_domain=DOMAIN, - translation_key="connection_failed", - translation_placeholders={ - "title": entry.title, - "host": entry.data[CONF_HOST], - }, - ) from exception - except (DataMissingException, TimeoutError) as exception: - raise ConfigEntryNotReady( - translation_domain=DOMAIN, - translation_key="timeout", - translation_placeholders={ - "title": entry.title, - "host": entry.data[CONF_HOST], - }, - ) from exception - - # Fetch initial data so we have data when entities subscribe - await coordinator.async_config_entry_first_refresh() - - entry.runtime_data = coordinator - - # Set up all platforms except notify - await hass.config_entries.async_forward_entry_setups( - entry, [platform for platform in PLATFORMS if platform != Platform.NOTIFY] - ) - - # Set up notify platform - hass.async_create_task( - discovery.async_load_platform( - hass, - Platform.NOTIFY, - DOMAIN, - { - CONF_NAME: f"{DOMAIN}_{coordinator.data.system.hostname}", - CONF_ENTITY_ID: entry.entry_id, - }, - {}, - ) - ) - - if hass.services.has_service(DOMAIN, SERVICE_OPEN_URL): - return True +async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: + """Set up System Bridge services.""" def valid_device(device: str) -> str: """Check device is valid.""" @@ -248,13 +122,13 @@ def valid_device(device: str) -> str: for entry in hass.config_entries.async_entries(DOMAIN) if entry.entry_id in device_entry.config_entries ) - except StopIteration as exception: - raise HomeAssistantError( + except StopIteration as err: + raise ServiceValidationError( translation_domain=DOMAIN, translation_key="device_not_found", translation_placeholders={"device": device}, - ) from exception - raise HomeAssistantError( + ) from err + raise ServiceValidationError( translation_domain=DOMAIN, translation_key="device_not_found", translation_placeholders={"device": device}, @@ -266,7 +140,6 @@ async def handle_get_process_by_id(service_call: ServiceCall) -> ServiceResponse coordinator = _get_coordinator(hass, service_call.data[CONF_BRIDGE]) processes: list[Process] = coordinator.data.processes - # Find process.id from list, raise ServiceValidationError if not found try: return asdict( next( @@ -275,12 +148,12 @@ async def handle_get_process_by_id(service_call: ServiceCall) -> ServiceResponse if process.id == service_call.data[CONF_ID] ) ) - except StopIteration as exception: + except StopIteration as err: raise ServiceValidationError( translation_domain=DOMAIN, translation_key="process_not_found", translation_placeholders={"id": service_call.data[CONF_ID]}, - ) from exception + ) from err async def handle_get_processes_by_name( service_call: ServiceCall, @@ -289,7 +162,6 @@ async def handle_get_processes_by_name( _LOGGER.debug("Get process by name: %s", service_call.data) coordinator = _get_coordinator(hass, service_call.data[CONF_BRIDGE]) - # Find processes from list items: list[dict[str, Any]] = [ asdict(process) for process in coordinator.data.processes @@ -339,14 +211,13 @@ async def handle_send_keypress(service_call: ServiceCall) -> ServiceResponse: return asdict(response) async def handle_send_text(service_call: ServiceCall) -> ServiceResponse: - """Handle the send_keypress service call.""" + """Handle the send_text service call.""" coordinator = _get_coordinator(hass, service_call.data[CONF_BRIDGE]) response = await coordinator.websocket_client.keyboard_text( KeyboardText(text=service_call.data[CONF_TEXT]) ) return asdict(response) - # pylint: disable-next=home-assistant-service-registered-in-setup-entry hass.services.async_register( DOMAIN, SERVICE_GET_PROCESS_BY_ID, @@ -360,7 +231,6 @@ async def handle_send_text(service_call: ServiceCall) -> ServiceResponse: supports_response=SupportsResponse.ONLY, ) - # pylint: disable-next=home-assistant-service-registered-in-setup-entry hass.services.async_register( DOMAIN, SERVICE_GET_PROCESSES_BY_NAME, @@ -374,7 +244,6 @@ async def handle_send_text(service_call: ServiceCall) -> ServiceResponse: supports_response=SupportsResponse.ONLY, ) - # pylint: disable-next=home-assistant-service-registered-in-setup-entry hass.services.async_register( DOMAIN, SERVICE_OPEN_PATH, @@ -388,7 +257,6 @@ async def handle_send_text(service_call: ServiceCall) -> ServiceResponse: supports_response=SupportsResponse.ONLY, ) - # pylint: disable-next=home-assistant-service-registered-in-setup-entry hass.services.async_register( DOMAIN, SERVICE_POWER_COMMAND, @@ -402,7 +270,6 @@ async def handle_send_text(service_call: ServiceCall) -> ServiceResponse: supports_response=SupportsResponse.ONLY, ) - # pylint: disable-next=home-assistant-service-registered-in-setup-entry hass.services.async_register( DOMAIN, SERVICE_OPEN_URL, @@ -416,7 +283,6 @@ async def handle_send_text(service_call: ServiceCall) -> ServiceResponse: supports_response=SupportsResponse.ONLY, ) - # pylint: disable-next=home-assistant-service-registered-in-setup-entry hass.services.async_register( DOMAIN, SERVICE_SEND_KEYPRESS, @@ -433,7 +299,6 @@ async def handle_send_text(service_call: ServiceCall) -> ServiceResponse: }, ) - # pylint: disable-next=home-assistant-service-registered-in-setup-entry hass.services.async_register( DOMAIN, SERVICE_SEND_TEXT, @@ -447,6 +312,137 @@ async def handle_send_text(service_call: ServiceCall) -> ServiceResponse: supports_response=SupportsResponse.ONLY, ) + return True + + +async def async_setup_entry( + hass: HomeAssistant, + entry: SystemBridgeConfigEntry, +) -> bool: + """Set up System Bridge from a config entry.""" + + # Check version before initialising + version = Version( + entry.data[CONF_HOST], + entry.data[CONF_PORT], + entry.data[CONF_TOKEN], + session=async_get_clientsession(hass), + ) + supported = False + try: + async with asyncio.timeout(DATA_WAIT_TIMEOUT): + supported = await version.check_supported() + except AuthenticationException as exception: + _LOGGER.error("Authentication failed for %s: %s", entry.title, exception) + raise ConfigEntryAuthFailed( + translation_domain=DOMAIN, + translation_key="authentication_failed", + translation_placeholders={ + "title": entry.title, + "host": entry.data[CONF_HOST], + }, + ) from exception + except (ConnectionClosedException, ConnectionErrorException) as exception: + raise ConfigEntryNotReady( + translation_domain=DOMAIN, + translation_key="connection_failed", + translation_placeholders={ + "title": entry.title, + "host": entry.data[CONF_HOST], + }, + ) from exception + except TimeoutError as exception: + raise ConfigEntryNotReady( + translation_domain=DOMAIN, + translation_key="timeout", + translation_placeholders={ + "title": entry.title, + "host": entry.data[CONF_HOST], + }, + ) from exception + + # If not supported, create an issue and raise ConfigEntryNotReady + if not supported: + async_create_issue( + hass=hass, + domain=DOMAIN, + issue_id=f"system_bridge_{entry.entry_id}_unsupported_version", + translation_key="unsupported_version", + translation_placeholders={"host": entry.data[CONF_HOST]}, + severity=IssueSeverity.ERROR, + is_fixable=False, + ) + raise ConfigEntryNotReady( + translation_domain=DOMAIN, + translation_key="unsupported_version", + translation_placeholders={ + "title": entry.title, + "host": entry.data[CONF_HOST], + }, + ) + + coordinator = SystemBridgeDataUpdateCoordinator( + hass, + _LOGGER, + entry=entry, + ) + + try: + async with asyncio.timeout(DATA_WAIT_TIMEOUT): + await coordinator.async_get_data(MODULES) + except AuthenticationException as exception: + _LOGGER.error("Authentication failed for %s: %s", entry.title, exception) + raise ConfigEntryAuthFailed( + translation_domain=DOMAIN, + translation_key="authentication_failed", + translation_placeholders={ + "title": entry.title, + "host": entry.data[CONF_HOST], + }, + ) from exception + except (ConnectionClosedException, ConnectionErrorException) as exception: + raise ConfigEntryNotReady( + translation_domain=DOMAIN, + translation_key="connection_failed", + translation_placeholders={ + "title": entry.title, + "host": entry.data[CONF_HOST], + }, + ) from exception + except (DataMissingException, TimeoutError) as exception: + raise ConfigEntryNotReady( + translation_domain=DOMAIN, + translation_key="timeout", + translation_placeholders={ + "title": entry.title, + "host": entry.data[CONF_HOST], + }, + ) from exception + + # Fetch initial data so we have data when entities subscribe + await coordinator.async_config_entry_first_refresh() + + entry.runtime_data = coordinator + + # Set up all platforms except notify + await hass.config_entries.async_forward_entry_setups( + entry, [platform for platform in PLATFORMS if platform != Platform.NOTIFY] + ) + + # Set up notify platform + hass.async_create_task( + discovery.async_load_platform( + hass, + Platform.NOTIFY, + DOMAIN, + { + CONF_NAME: f"{DOMAIN}_{coordinator.data.system.hostname}", + CONF_ENTITY_ID: entry.entry_id, + }, + {}, + ) + ) + # Reload entry when its updated. entry.async_on_unload(entry.add_update_listener(async_reload_entry))