From 84d540bf221f94c1dc654c0ae178a063a0f7e612 Mon Sep 17 00:00:00 2001 From: rikroe Date: Wed, 3 May 2023 22:51:48 +0200 Subject: [PATCH] Improve internet/API error handling for BMW (home-assistant/core#90274) --- .../bmw_connected_drive/config_flow.py | 13 +++++-- .../bmw_connected_drive/coordinator.py | 35 +++++++++---------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/custom_components/bmw_connected_drive/config_flow.py b/custom_components/bmw_connected_drive/config_flow.py index 0cde37b..eb58a6c 100644 --- a/custom_components/bmw_connected_drive/config_flow.py +++ b/custom_components/bmw_connected_drive/config_flow.py @@ -6,7 +6,8 @@ from bimmer_connected.api.authentication import MyBMWAuthentication from bimmer_connected.api.regions import get_region_from_name -from httpx import HTTPError +from bimmer_connected.models import MyBMWAPIError, MyBMWAuthError +from httpx import RequestError import voluptuous as vol from homeassistant import config_entries, core, exceptions @@ -41,7 +42,9 @@ async def validate_input( try: await auth.login() - except HTTPError as ex: + except MyBMWAuthError as ex: + raise InvalidAuth from ex + except (MyBMWAPIError, RequestError) as ex: raise CannotConnect from ex # Return info that you want to store in the config entry. @@ -80,6 +83,8 @@ async def async_step_user( } except CannotConnect: errors["base"] = "cannot_connect" + except InvalidAuth: + errors["base"] = "invalid_auth" if info: if self._reauth_entry: @@ -160,3 +165,7 @@ async def async_step_account_options( class CannotConnect(exceptions.HomeAssistantError): """Error to indicate we cannot connect.""" + + +class InvalidAuth(exceptions.HomeAssistantError): + """Error to indicate there is invalid auth.""" diff --git a/custom_components/bmw_connected_drive/coordinator.py b/custom_components/bmw_connected_drive/coordinator.py index ae139d4..ab06d50 100644 --- a/custom_components/bmw_connected_drive/coordinator.py +++ b/custom_components/bmw_connected_drive/coordinator.py @@ -6,14 +6,14 @@ from bimmer_connected.account import MyBMWAccount from bimmer_connected.api.regions import get_region_from_name -from bimmer_connected.models import GPSPosition -from httpx import HTTPError, HTTPStatusError, TimeoutException +from bimmer_connected.models import GPSPosition, MyBMWAPIError, MyBMWAuthError +from httpx import RequestError from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_PASSWORD, CONF_REGION, CONF_USERNAME from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator from .const import CONF_READ_ONLY, CONF_REFRESH_TOKEN, DOMAIN @@ -56,20 +56,19 @@ async def _async_update_data(self) -> None: try: await self.account.get_vehicles() - except (HTTPError, HTTPStatusError, TimeoutException) as err: - if isinstance(err, HTTPStatusError) and err.response.status_code == 429: - # Increase scan interval to not jump to not bring up the issue next time - self.update_interval = timedelta( - seconds=DEFAULT_SCAN_INTERVAL_SECONDS * 3 + except MyBMWAuthError as err: + # Clear refresh token and trigger reauth + self._update_config_entry_refresh_token(None) + raise ConfigEntryAuthFailed(str(err)) from err + + except (MyBMWAPIError, RequestError) as err: + if self.last_update_success is True: + _LOGGER.warning( + "Error communicating with BMW API (%s): %s", + type(err).__name__, + err, ) - if isinstance(err, HTTPStatusError) and err.response.status_code in ( - 401, - 403, - ): - # Clear refresh token only and trigger reauth - self._update_config_entry_refresh_token(None) - raise ConfigEntryAuthFailed(str(err)) from err - raise UpdateFailed(f"Error communicating with BMW API: {err}") from err + self.last_update_success = False if self.account.refresh_token != old_refresh_token: self._update_config_entry_refresh_token(self.account.refresh_token) @@ -79,8 +78,8 @@ async def _async_update_data(self) -> None: self.account.refresh_token, ) - # Reset scan interval after successful update - self.update_interval = timedelta(seconds=DEFAULT_SCAN_INTERVAL_SECONDS) + if self.last_update_success is False: + _LOGGER.info("Reconnected to BMW API") def _update_config_entry_refresh_token(self, refresh_token: str | None) -> None: """Update or delete the refresh_token in the Config Entry."""