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
This commit is contained in:
jrester 2020-04-27 02:14:53 +02:00 committed by GitHub
parent aa2bfbb541
commit 4b998ea6af
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 76 additions and 21 deletions

View File

@ -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,
}
)

View File

@ -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."""

View File

@ -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"

View File

@ -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" }

View File

@ -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": {

View File

@ -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"}