Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 39 additions & 5 deletions homeassistant/components/powerwall/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
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
Expand All @@ -13,10 +13,11 @@
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,
Expand Down Expand Up @@ -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.
Expand All @@ -87,6 +88,17 @@ def _async_migrator(entity_entry: entity_registry.RegistryEntry):
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."""

Expand All @@ -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
Comment thread
bdraco marked this conversation as resolved.
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")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be debug level.

if not hass.data[DOMAIN][entry.entry_id][POWERWALL_API_CHANGED]:
_LOGGER.info("Updating data")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here too.

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,
Expand All @@ -122,6 +155,7 @@ async def async_update_data():
POWERWALL_OBJECT: power_wall,
POWERWALL_COORDINATOR: coordinator,
POWERWALL_HTTP_SESSION: http_session,
POWERWALL_API_CHANGED: False,
}
)

Expand Down
14 changes: 12 additions & 2 deletions homeassistant/components/powerwall/config_flow.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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}
Expand All @@ -46,6 +50,8 @@ async def async_step_user(self, user_input=None):
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"
Expand All @@ -69,3 +75,7 @@ async def async_step_import(self, user_input):

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."""
13 changes: 1 addition & 12 deletions homeassistant/components/powerwall/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

POWERWALL_OBJECT = "powerwall"
POWERWALL_COORDINATOR = "coordinator"
POWERWALL_API_CHANGED = "api_changed"

UPDATE_INTERVAL = 30

Expand All @@ -15,14 +16,6 @@
ATTR_INSTANT_AVERAGE_VOLTAGE = "instant_average_voltage"
ATTR_NOMINAL_SYSTEM_POWER = "nominal_system_power_kW"

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"
Expand All @@ -38,10 +31,6 @@

POWERWALL_HTTP_SESSION = "http_session"

POWERWALL_GRID_ONLINE = "SystemGridConnected"
POWERWALL_CONNECTED_KEY = "connected_to_tesla"
POWERWALL_RUNNING_KEY = "running"

POWERWALL_BATTERY_METER = "battery"

# We only declare charging if they are getting
Expand Down
1 change: 1 addition & 0 deletions homeassistant/components/powerwall/strings.json
Original file line number Diff line number Diff line change
Expand Up @@ -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" }
Expand Down
3 changes: 2 additions & 1 deletion homeassistant/components/powerwall/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
22 changes: 21 additions & 1 deletion tests/components/powerwall/test_config_flow.py
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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"}