From 4b998ea6afa5b085fe1ef94bacdac4d723a5189b Mon Sep 17 00:00:00 2001 From: jrester <31157644+jrester@users.noreply.github.com> Date: Mon, 27 Apr 2020 02:14:53 +0200 Subject: [PATCH] Improve error handling for Powerwall (#34580) * Improve error handling modified: homeassistant/components/powerwall/__init__.py modified: homeassistant/components/powerwall/config_flow.py modified: homeassistant/components/powerwall/const.py modified: homeassistant/components/powerwall/strings.json modified: homeassistant/components/powerwall/translations/en.json * Change exception name modified: homeassistant/components/powerwall/__init__.py modified: homeassistant/components/powerwall/config_flow.py * Add test * Rename PowerwallError to POWERWALL_ERROR * Modify handling of APIChangedErrors modified: homeassistant/components/powerwall/__init__.py modified: homeassistant/components/powerwall/const.py --- .../components/powerwall/__init__.py | 44 ++++++++++++++++--- .../components/powerwall/config_flow.py | 14 +++++- homeassistant/components/powerwall/const.py | 13 +----- .../components/powerwall/strings.json | 1 + .../components/powerwall/translations/en.json | 3 +- .../components/powerwall/test_config_flow.py | 22 +++++++++- 6 files changed, 76 insertions(+), 21 deletions(-) diff --git a/homeassistant/components/powerwall/__init__.py b/homeassistant/components/powerwall/__init__.py index a76393e350a..336c3ac0bff 100644 --- a/homeassistant/components/powerwall/__init__.py +++ b/homeassistant/components/powerwall/__init__.py @@ -4,7 +4,7 @@ from datetime import timedelta import logging import requests -from tesla_powerwall import APIError, Powerwall, PowerwallUnreachableError +from tesla_powerwall import APIChangedError, Powerwall, PowerwallUnreachableError import voluptuous as vol from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry @@ -13,10 +13,11 @@ from homeassistant.core import HomeAssistant, callback from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.helpers import entity_registry import homeassistant.helpers.config_validation as cv -from homeassistant.helpers.update_coordinator import DataUpdateCoordinator +from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from .const import ( DOMAIN, + POWERWALL_API_CHANGED, POWERWALL_API_CHARGE, POWERWALL_API_DEVICE_TYPE, POWERWALL_API_GRID_STATUS, @@ -64,7 +65,7 @@ async def _migrate_old_unique_ids(hass, entry_id, powerwall_data): @callback def _async_migrator(entity_entry: entity_registry.RegistryEntry): parts = entity_entry.unique_id.split("_") - # Check if the unique_id starts with the serial_numbers of the powerwakks + # Check if the unique_id starts with the serial_numbers of the powerwalls if parts[0 : len(serial_numbers)] != serial_numbers: # The old unique_id ended with the nomianal_system_engery_kWh so we can use that # to find the old base unique_id and extract the device_suffix. @@ -87,6 +88,17 @@ async def _migrate_old_unique_ids(hass, entry_id, powerwall_data): await entity_registry.async_migrate_entries(hass, entry_id, _async_migrator) +async def _async_handle_api_changed_error(hass: HomeAssistant, error: APIChangedError): + # The error might include some important information about what exactly changed. + _LOGGER.error(str(error)) + hass.components.persistent_notification.async_create( + "It seems like your powerwall uses an unsupported version. " + "Please update the software of your powerwall or if it is" + "already the newest consider reporting this issue.\nSee logs for more information", + title="Unknown powerwall software version", + ) + + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): """Set up Tesla Powerwall from a config entry.""" @@ -97,16 +109,37 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): power_wall = Powerwall(entry.data[CONF_IP_ADDRESS], http_session=http_session) try: await hass.async_add_executor_job(power_wall.detect_and_pin_version) + await hass.async_add_executor_job(_fetch_powerwall_data, power_wall) powerwall_data = await hass.async_add_executor_job(call_base_info, power_wall) - except (PowerwallUnreachableError, APIError, ConnectionError): + except PowerwallUnreachableError: http_session.close() raise ConfigEntryNotReady + except APIChangedError as err: + http_session.close() + await _async_handle_api_changed_error(hass, err) + return False await _migrate_old_unique_ids(hass, entry_id, powerwall_data) async def async_update_data(): """Fetch data from API endpoint.""" - return await hass.async_add_executor_job(_fetch_powerwall_data, power_wall) + # Check if we had an error before + _LOGGER.info("Checking if update failed") + if not hass.data[DOMAIN][entry.entry_id][POWERWALL_API_CHANGED]: + _LOGGER.info("Updating data") + try: + return await hass.async_add_executor_job( + _fetch_powerwall_data, power_wall + ) + except PowerwallUnreachableError: + raise UpdateFailed("Unable to fetch data from powerwall") + except APIChangedError as err: + await _async_handle_api_changed_error(hass, err) + hass.data[DOMAIN][entry.entry_id][POWERWALL_API_CHANGED] = True + # Returns the cached data. This data can also be None + return hass.data[DOMAIN][entry.entry_id][POWERWALL_COORDINATOR].data + else: + return hass.data[DOMAIN][entry.entry_id][POWERWALL_COORDINATOR].data coordinator = DataUpdateCoordinator( hass, @@ -122,6 +155,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): POWERWALL_OBJECT: power_wall, POWERWALL_COORDINATOR: coordinator, POWERWALL_HTTP_SESSION: http_session, + POWERWALL_API_CHANGED: False, } ) diff --git a/homeassistant/components/powerwall/config_flow.py b/homeassistant/components/powerwall/config_flow.py index ca0e2143454..8c313b79024 100644 --- a/homeassistant/components/powerwall/config_flow.py +++ b/homeassistant/components/powerwall/config_flow.py @@ -1,7 +1,7 @@ """Config flow for Tesla Powerwall integration.""" import logging -from tesla_powerwall import APIError, Powerwall, PowerwallUnreachableError +from tesla_powerwall import APIChangedError, Powerwall, PowerwallUnreachableError import voluptuous as vol from homeassistant import config_entries, core, exceptions @@ -25,8 +25,12 @@ async def validate_input(hass: core.HomeAssistant, data): try: await hass.async_add_executor_job(power_wall.detect_and_pin_version) site_info = await hass.async_add_executor_job(power_wall.get_site_info) - except (PowerwallUnreachableError, APIError, ConnectionError): + except PowerwallUnreachableError: raise CannotConnect + except APIChangedError as err: + # Only log the exception without the traceback + _LOGGER.error(str(err)) + raise WrongVersion # Return info that you want to store in the config entry. return {"title": site_info.site_name} @@ -46,6 +50,8 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): info = await validate_input(self.hass, user_input) except CannotConnect: errors["base"] = "cannot_connect" + except WrongVersion: + errors["base"] = "wrong_version" except Exception: # pylint: disable=broad-except _LOGGER.exception("Unexpected exception") errors["base"] = "unknown" @@ -69,3 +75,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): class CannotConnect(exceptions.HomeAssistantError): """Error to indicate we cannot connect.""" + + +class WrongVersion(exceptions.HomeAssistantError): + """Error to indicate the powerwall uses a software version we cannot interact with.""" diff --git a/homeassistant/components/powerwall/const.py b/homeassistant/components/powerwall/const.py index e2daf1e3760..5f0e9ae3b35 100644 --- a/homeassistant/components/powerwall/const.py +++ b/homeassistant/components/powerwall/const.py @@ -4,6 +4,7 @@ DOMAIN = "powerwall" POWERWALL_OBJECT = "powerwall" POWERWALL_COORDINATOR = "coordinator" +POWERWALL_API_CHANGED = "api_changed" UPDATE_INTERVAL = 30 @@ -16,14 +17,6 @@ ATTR_INSTANT_AVERAGE_VOLTAGE = "instant_average_voltage" ATTR_NOMINAL_SYSTEM_POWER = "nominal_system_power_kW" ATTR_IS_ACTIVE = "is_active" -SITE_INFO_UTILITY = "utility" -SITE_INFO_GRID_CODE = "grid_code" -SITE_INFO_NOMINAL_SYSTEM_POWER_KW = "nominal_system_power_kW" -SITE_INFO_NOMINAL_SYSTEM_ENERGY_KWH = "nominal_system_energy_kWh" -SITE_INFO_REGION = "region" - -DEVICE_TYPE_DEVICE_TYPE = "device_type" - STATUS_VERSION = "version" POWERWALL_SITE_NAME = "site_name" @@ -39,10 +32,6 @@ POWERWALL_API_SERIAL_NUMBERS = "serial_numbers" POWERWALL_HTTP_SESSION = "http_session" -POWERWALL_GRID_ONLINE = "SystemGridConnected" -POWERWALL_CONNECTED_KEY = "connected_to_tesla" -POWERWALL_RUNNING_KEY = "running" - POWERWALL_BATTERY_METER = "battery" MODEL = "PowerWall 2" diff --git a/homeassistant/components/powerwall/strings.json b/homeassistant/components/powerwall/strings.json index bb5ed671435..ce7e6a1965f 100644 --- a/homeassistant/components/powerwall/strings.json +++ b/homeassistant/components/powerwall/strings.json @@ -8,6 +8,7 @@ }, "error": { "cannot_connect": "Failed to connect, please try again", + "wrong_version": "Your powerwall uses a software version that is not supported. Please consider upgrading or reporting this issue so it can be resolved.", "unknown": "Unexpected error" }, "abort": { "already_configured": "The powerwall is already configured" } diff --git a/homeassistant/components/powerwall/translations/en.json b/homeassistant/components/powerwall/translations/en.json index 378abf486ad..8b45c665d85 100644 --- a/homeassistant/components/powerwall/translations/en.json +++ b/homeassistant/components/powerwall/translations/en.json @@ -5,7 +5,8 @@ }, "error": { "cannot_connect": "Failed to connect, please try again", - "unknown": "Unexpected error" + "unknown": "Unexpected error", + "wrong_version": "Your powerwall uses a software version that is not supported. Please consider upgrading or reporting this issue so it can be resolved." }, "step": { "user": { diff --git a/tests/components/powerwall/test_config_flow.py b/tests/components/powerwall/test_config_flow.py index a6752d838f3..097346c5ac7 100644 --- a/tests/components/powerwall/test_config_flow.py +++ b/tests/components/powerwall/test_config_flow.py @@ -1,7 +1,7 @@ """Test the Powerwall config flow.""" from asynctest import patch -from tesla_powerwall import PowerwallUnreachableError +from tesla_powerwall import APIChangedError, PowerwallUnreachableError from homeassistant import config_entries, setup from homeassistant.components.powerwall.const import DOMAIN @@ -86,3 +86,23 @@ async def test_form_cannot_connect(hass): assert result2["type"] == "form" assert result2["errors"] == {"base": "cannot_connect"} + + +async def test_form_wrong_version(hass): + """Test we can handle wrong version error.""" + result = await hass.config_entries.flow.async_init( + DOMAIN, context={"source": config_entries.SOURCE_USER} + ) + + mock_powerwall = _mock_powerwall_side_effect(site_info=APIChangedError(object, {})) + + with patch( + "homeassistant.components.powerwall.config_flow.Powerwall", + return_value=mock_powerwall, + ): + result3 = await hass.config_entries.flow.async_configure( + result["flow_id"], {CONF_IP_ADDRESS: "1.2.3.4"}, + ) + + assert result3["type"] == "form" + assert result3["errors"] == {"base": "wrong_version"}