mirror of
https://github.com/home-assistant/core.git
synced 2025-07-25 06:07:17 +00:00
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:
parent
aa2bfbb541
commit
4b998ea6af
@ -4,7 +4,7 @@ from datetime import timedelta
|
|||||||
import logging
|
import logging
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from tesla_powerwall import APIError, Powerwall, PowerwallUnreachableError
|
from tesla_powerwall import APIChangedError, Powerwall, PowerwallUnreachableError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant.config_entries import SOURCE_IMPORT, ConfigEntry
|
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.exceptions import ConfigEntryNotReady
|
||||||
from homeassistant.helpers import entity_registry
|
from homeassistant.helpers import entity_registry
|
||||||
import homeassistant.helpers.config_validation as cv
|
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 (
|
from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
|
POWERWALL_API_CHANGED,
|
||||||
POWERWALL_API_CHARGE,
|
POWERWALL_API_CHARGE,
|
||||||
POWERWALL_API_DEVICE_TYPE,
|
POWERWALL_API_DEVICE_TYPE,
|
||||||
POWERWALL_API_GRID_STATUS,
|
POWERWALL_API_GRID_STATUS,
|
||||||
@ -64,7 +65,7 @@ async def _migrate_old_unique_ids(hass, entry_id, powerwall_data):
|
|||||||
@callback
|
@callback
|
||||||
def _async_migrator(entity_entry: entity_registry.RegistryEntry):
|
def _async_migrator(entity_entry: entity_registry.RegistryEntry):
|
||||||
parts = entity_entry.unique_id.split("_")
|
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:
|
if parts[0 : len(serial_numbers)] != serial_numbers:
|
||||||
# The old unique_id ended with the nomianal_system_engery_kWh so we can use that
|
# 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.
|
# 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)
|
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):
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
||||||
"""Set up Tesla Powerwall from a config entry."""
|
"""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)
|
power_wall = Powerwall(entry.data[CONF_IP_ADDRESS], http_session=http_session)
|
||||||
try:
|
try:
|
||||||
await hass.async_add_executor_job(power_wall.detect_and_pin_version)
|
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)
|
powerwall_data = await hass.async_add_executor_job(call_base_info, power_wall)
|
||||||
except (PowerwallUnreachableError, APIError, ConnectionError):
|
except PowerwallUnreachableError:
|
||||||
http_session.close()
|
http_session.close()
|
||||||
raise ConfigEntryNotReady
|
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)
|
await _migrate_old_unique_ids(hass, entry_id, powerwall_data)
|
||||||
|
|
||||||
async def async_update_data():
|
async def async_update_data():
|
||||||
"""Fetch data from API endpoint."""
|
"""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(
|
coordinator = DataUpdateCoordinator(
|
||||||
hass,
|
hass,
|
||||||
@ -122,6 +155,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
|
|||||||
POWERWALL_OBJECT: power_wall,
|
POWERWALL_OBJECT: power_wall,
|
||||||
POWERWALL_COORDINATOR: coordinator,
|
POWERWALL_COORDINATOR: coordinator,
|
||||||
POWERWALL_HTTP_SESSION: http_session,
|
POWERWALL_HTTP_SESSION: http_session,
|
||||||
|
POWERWALL_API_CHANGED: False,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Config flow for Tesla Powerwall integration."""
|
"""Config flow for Tesla Powerwall integration."""
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from tesla_powerwall import APIError, Powerwall, PowerwallUnreachableError
|
from tesla_powerwall import APIChangedError, Powerwall, PowerwallUnreachableError
|
||||||
import voluptuous as vol
|
import voluptuous as vol
|
||||||
|
|
||||||
from homeassistant import config_entries, core, exceptions
|
from homeassistant import config_entries, core, exceptions
|
||||||
@ -25,8 +25,12 @@ async def validate_input(hass: core.HomeAssistant, data):
|
|||||||
try:
|
try:
|
||||||
await hass.async_add_executor_job(power_wall.detect_and_pin_version)
|
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)
|
site_info = await hass.async_add_executor_job(power_wall.get_site_info)
|
||||||
except (PowerwallUnreachableError, APIError, ConnectionError):
|
except PowerwallUnreachableError:
|
||||||
raise CannotConnect
|
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 info that you want to store in the config entry.
|
||||||
return {"title": site_info.site_name}
|
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)
|
info = await validate_input(self.hass, user_input)
|
||||||
except CannotConnect:
|
except CannotConnect:
|
||||||
errors["base"] = "cannot_connect"
|
errors["base"] = "cannot_connect"
|
||||||
|
except WrongVersion:
|
||||||
|
errors["base"] = "wrong_version"
|
||||||
except Exception: # pylint: disable=broad-except
|
except Exception: # pylint: disable=broad-except
|
||||||
_LOGGER.exception("Unexpected exception")
|
_LOGGER.exception("Unexpected exception")
|
||||||
errors["base"] = "unknown"
|
errors["base"] = "unknown"
|
||||||
@ -69,3 +75,7 @@ class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
|||||||
|
|
||||||
class CannotConnect(exceptions.HomeAssistantError):
|
class CannotConnect(exceptions.HomeAssistantError):
|
||||||
"""Error to indicate we cannot connect."""
|
"""Error to indicate we cannot connect."""
|
||||||
|
|
||||||
|
|
||||||
|
class WrongVersion(exceptions.HomeAssistantError):
|
||||||
|
"""Error to indicate the powerwall uses a software version we cannot interact with."""
|
||||||
|
@ -4,6 +4,7 @@ DOMAIN = "powerwall"
|
|||||||
|
|
||||||
POWERWALL_OBJECT = "powerwall"
|
POWERWALL_OBJECT = "powerwall"
|
||||||
POWERWALL_COORDINATOR = "coordinator"
|
POWERWALL_COORDINATOR = "coordinator"
|
||||||
|
POWERWALL_API_CHANGED = "api_changed"
|
||||||
|
|
||||||
UPDATE_INTERVAL = 30
|
UPDATE_INTERVAL = 30
|
||||||
|
|
||||||
@ -16,14 +17,6 @@ ATTR_INSTANT_AVERAGE_VOLTAGE = "instant_average_voltage"
|
|||||||
ATTR_NOMINAL_SYSTEM_POWER = "nominal_system_power_kW"
|
ATTR_NOMINAL_SYSTEM_POWER = "nominal_system_power_kW"
|
||||||
ATTR_IS_ACTIVE = "is_active"
|
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"
|
STATUS_VERSION = "version"
|
||||||
|
|
||||||
POWERWALL_SITE_NAME = "site_name"
|
POWERWALL_SITE_NAME = "site_name"
|
||||||
@ -39,10 +32,6 @@ POWERWALL_API_SERIAL_NUMBERS = "serial_numbers"
|
|||||||
|
|
||||||
POWERWALL_HTTP_SESSION = "http_session"
|
POWERWALL_HTTP_SESSION = "http_session"
|
||||||
|
|
||||||
POWERWALL_GRID_ONLINE = "SystemGridConnected"
|
|
||||||
POWERWALL_CONNECTED_KEY = "connected_to_tesla"
|
|
||||||
POWERWALL_RUNNING_KEY = "running"
|
|
||||||
|
|
||||||
POWERWALL_BATTERY_METER = "battery"
|
POWERWALL_BATTERY_METER = "battery"
|
||||||
|
|
||||||
MODEL = "PowerWall 2"
|
MODEL = "PowerWall 2"
|
||||||
|
@ -8,6 +8,7 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "Failed to connect, please try again",
|
"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"
|
"unknown": "Unexpected error"
|
||||||
},
|
},
|
||||||
"abort": { "already_configured": "The powerwall is already configured" }
|
"abort": { "already_configured": "The powerwall is already configured" }
|
||||||
|
@ -5,7 +5,8 @@
|
|||||||
},
|
},
|
||||||
"error": {
|
"error": {
|
||||||
"cannot_connect": "Failed to connect, please try again",
|
"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": {
|
"step": {
|
||||||
"user": {
|
"user": {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
"""Test the Powerwall config flow."""
|
"""Test the Powerwall config flow."""
|
||||||
|
|
||||||
from asynctest import patch
|
from asynctest import patch
|
||||||
from tesla_powerwall import PowerwallUnreachableError
|
from tesla_powerwall import APIChangedError, PowerwallUnreachableError
|
||||||
|
|
||||||
from homeassistant import config_entries, setup
|
from homeassistant import config_entries, setup
|
||||||
from homeassistant.components.powerwall.const import DOMAIN
|
from homeassistant.components.powerwall.const import DOMAIN
|
||||||
@ -86,3 +86,23 @@ async def test_form_cannot_connect(hass):
|
|||||||
|
|
||||||
assert result2["type"] == "form"
|
assert result2["type"] == "form"
|
||||||
assert result2["errors"] == {"base": "cannot_connect"}
|
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"}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user