Skip to content

Commit

Permalink
fix: Handle when account cant be found and display repair message
Browse files Browse the repository at this point in the history
  • Loading branch information
BottlecapDave committed Mar 26, 2023
1 parent 3cdb4b5 commit 7c7801a
Show file tree
Hide file tree
Showing 9 changed files with 98 additions and 21 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ To support Octopus Energy's [saving sessions](https://octopus.energy/saving-sess

### Target Rates

If you go through the [setup](https://my.home-assistant.io/redirect/config_flow_start/?domain=octopus_energy) process after you've configured your account, you can set up target rate sensors. These sensors calculate the lowest continuous or intermittent rates **within a 24 hour period** and turn on when these periods are active. These sensors can then be used in automations to turn on/off devices that save you (and the planet) energy and money.
If you go through the [setup](https://my.home-assistant.io/redirect/config_flow_start/?domain=octopus_energy) process after you've configured your account, you can set up target rate sensors. These sensors calculate the lowest continuous or intermittent rates **within a 24 hour period** and turn on when these periods are active. These sensors can then be used in automations to turn on/off devices that save you (and the planet) energy and money. You can go through this flow as many times as you need target rate sensors.

Each sensor will be in the form `binary_sensor.octopus_energy_target_{{TARGET_RATE_NAME}}`.

Expand Down
26 changes: 26 additions & 0 deletions _docs/repairs/account_not_found.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# Repairs - Account not found

If you receive the fixable warning around your account information not being found, there are two possible causes and solutions

## API Key is invalid

The simplest cause is that your API key has become invalid. This can happen if you have recently reset your API key in your [Octopus Energy dashboard](https://octopus.energy/dashboard/new/accounts/personal-details/api-access).

To fix
1. Navigate to [your integrations](https://my.home-assistant.io/redirect/integrations/)
2. Find 'Octopus Energy'. If you have target rate sensors set up, click on the entry marked either 'Octopus Energy' or 'Account'.
3. Click on 'Configure'
4. Update your API key with your new one displayed on your [Octopus Energy dashboard](https://octopus.energy/dashboard/new/accounts/personal-details/api-access).
5. Click 'Submit'

## Account id is invalid

If you have setup the integration with an account that is no longer valid, you will need to manually remove all entries for the integration.

To fix
1. Navigate to [your integrations](https://my.home-assistant.io/redirect/integrations/)
2. Find 'Octopus Energy'.
3. If you have no target rate sensors setup, click on the three dots and then click on 'Delete'
4. If you have target rate sensors setup, click on each entry within 'Octopus Energy', click on the three dots and then click on 'Delete'

Once done, you can then add the integration fresh with your new account.
29 changes: 25 additions & 4 deletions custom_components/octopus_energy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
DataUpdateCoordinator
)

from homeassistant.helpers import issue_registry as ir

from .const import (
DOMAIN,

Expand Down Expand Up @@ -68,12 +70,31 @@ async def async_setup_entry(hass, entry):

return True

async def async_get_current_electricity_agreement_tariff_codes(client: OctopusEnergyApiClient, account_id: str):
account_info = await client.async_get_account(account_id)
async def async_get_current_electricity_agreement_tariff_codes(hass, client: OctopusEnergyApiClient, account_id: str):
account_info = None
try:
account_info = await client.async_get_account(account_id)
except:
# count exceptions as failure to retrieve account
_LOGGER.debug('Failed to retrieve account')

if account_info is None:
ir.async_create_issue(
hass,
DOMAIN,
f"account_not_found_{account_id}",
is_fixable=False,
severity=ir.IssueSeverity.ERROR,
learn_more_url="https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/blob/develop/_docs/repairs/account_not_found.md",
translation_key="account_not_found",
translation_placeholders={ "account_id": account_id },
)
else:
ir.async_delete_issue(hass, DOMAIN, f"account_not_found_{account_id}")

tariff_codes = {}
current = now()
if len(account_info["electricity_meter_points"]) > 0:
if account_info is not None and len(account_info["electricity_meter_points"]) > 0:
for point in account_info["electricity_meter_points"]:
active_tariff_code = get_active_tariff_code(current, point["agreements"])
# The type of meter (ie smart vs dumb) can change the tariff behaviour, so we
Expand Down Expand Up @@ -128,7 +149,7 @@ async def async_update_electricity_rates_data():
client: OctopusEnergyApiClient = hass.data[DOMAIN][DATA_CLIENT]
if (DATA_RATES not in hass.data[DOMAIN] or (current.minute % 30) == 0 or len(hass.data[DOMAIN][DATA_RATES]) == 0):

tariff_codes = await async_get_current_electricity_agreement_tariff_codes(client, account_id)
tariff_codes = await async_get_current_electricity_agreement_tariff_codes(hass, client, account_id)
_LOGGER.debug(f'tariff_codes: {tariff_codes}')

period_from = as_utc(current.replace(hour=0, minute=0, second=0, microsecond=0))
Expand Down
16 changes: 12 additions & 4 deletions custom_components/octopus_energy/api_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
meterPoint {{
mpan
meters(includeInactive: false) {{
makeAndType
serialNumber
smartExportElectricityMeter {{
deviceId
Expand Down Expand Up @@ -142,12 +143,16 @@ async def async_refresh_token(self):
payload = { "query": api_token_query.format(api_key=self._api_key) }
async with client.post(url, json=payload) as token_response:
token_response_body = await self.__async_read_response(token_response, url)
if (token_response_body != None and "data" in token_response_body):
if (token_response_body != None and
"data" in token_response_body and
"obtainKrakenToken" in token_response_body["data"] and
token_response_body["data"]["obtainKrakenToken"] is not None and
"token" in token_response_body["data"]["obtainKrakenToken"]):

self._graphql_token = token_response_body["data"]["obtainKrakenToken"]["token"]
self._graphql_expiration = now() + timedelta(hours=1)
else:
_LOGGER.error("Failed to retrieve auth token")
raise Exception('Failed to refresh token')

async def async_get_account(self, account_id):
"""Get the user's account"""
Expand All @@ -161,9 +166,12 @@ async def async_get_account(self, account_id):
async with client.post(url, json=payload, headers=headers) as account_response:
account_response_body = await self.__async_read_response(account_response, url)

_LOGGER.debug(account_response_body)
_LOGGER.debug(f'account: {account_response_body}')

if (account_response_body != None and "data" in account_response_body):
if (account_response_body != None and
"data" in account_response_body and
"account" in account_response_body["data"] and
account_response_body["data"]["account"] is not None):
return {
"electricity_meter_points": list(map(lambda mp: {
"mpan": mp["meterPoint"]["mpan"],
Expand Down
6 changes: 4 additions & 2 deletions custom_components/octopus_energy/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ async def async_setup_target_rate_schema(self):

meters = []
now = utcnow()
if len(account_info["electricity_meter_points"]) > 0:
if account_info is not None and len(account_info["electricity_meter_points"]) > 0:
for point in account_info["electricity_meter_points"]:
active_tariff_code = get_active_tariff_code(now, point["agreements"])
if active_tariff_code != None:
Expand Down Expand Up @@ -197,10 +197,12 @@ def __init__(self, entry) -> None:
async def __async_setup_target_rate_schema(self, config, errors):
client = self.hass.data[DOMAIN][DATA_CLIENT]
account_info = await client.async_get_account(self.hass.data[DOMAIN][DATA_ACCOUNT_ID])
if account_info is None:
errors[CONFIG_TARGET_MPAN] = "account_not_found"

meters = []
now = utcnow()
if len(account_info["electricity_meter_points"]) > 0:
if account_info is not None and len(account_info["electricity_meter_points"]) > 0:
for point in account_info["electricity_meter_points"]:
active_tariff_code = get_active_tariff_code(now, point["agreements"])
if active_tariff_code != None:
Expand Down
8 changes: 4 additions & 4 deletions custom_components/octopus_energy/diagnostics.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ async def async_get_device_diagnostics(hass, config_entry, device):

account_info = await client.async_get_account(hass.data[DOMAIN][DATA_ACCOUNT_ID])

points_length = len(account_info["electricity_meter_points"])
if points_length > 0:
points_length = account_info is not None and len(account_info["electricity_meter_points"])
if account_info is not None and points_length > 0:
for point_index in range(points_length):
account_info["electricity_meter_points"][point_index] = async_redact_data(account_info["electricity_meter_points"][point_index], { "mpan" })
meters_length = len(account_info["electricity_meter_points"][point_index]["meters"])
for meter_index in range(meters_length):
account_info["electricity_meter_points"][point_index]["meters"][meter_index] = async_redact_data(account_info["electricity_meter_points"][point_index]["meters"][meter_index], { "serial_number", "device_id" })

points_length = len(account_info["gas_meter_points"])
if points_length > 0:
points_length = account_info is not None and len(account_info["gas_meter_points"])
if account_info is not None and points_length > 0:
for point_index in range(points_length):
account_info["gas_meter_points"][point_index] = async_redact_data(account_info["gas_meter_points"][point_index], { "mprn" })
meters_length = len(account_info["gas_meter_points"][point_index]["meters"])
Expand Down
2 changes: 1 addition & 1 deletion custom_components/octopus_energy/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"@bottlecapdave"
],
"config_flow": true,
"dependencies": [],
"dependencies": ["repairs"],
"documentation": "https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/",
"homekit": {},
"iot_class": "cloud_polling",
Expand Down
14 changes: 10 additions & 4 deletions custom_components/octopus_energy/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
"Account Id": "Your account Id (e.g. A-AAAA1111)",
"supports_live_consumption": "I have a Home Mini",
"calorific_value": "Gas calorific value. This can be found on your gas statement and can change from time to time.",
"electricity_price_cap": "Electricity price cap in pence",
"gas_price_cap": "Gas price cap in pence"
"electricity_price_cap": "Optional electricity price cap in pence",
"gas_price_cap": "Optional gas price cap in pence"
}
},
"target_rate": {
Expand Down Expand Up @@ -47,9 +47,9 @@
"Api key": "Api key",
"supports_live_consumption": "I have a Home Mini",
"calorific_value": "Gas calorific value. This can be found on your gas statement and can change from time to time.",
"electricity_price_cap": "Electricity price cap in pence",
"electricity_price_cap": "Optional electricity price cap in pence",
"clear_electricity_price_cap": "Clear electricity price cap",
"gas_price_cap": "Gas price cap in pence",
"gas_price_cap": "Optional gas price cap in pence",
"clear_gas_price_cap": "Clear Gas price cap"
}
},
Expand All @@ -74,5 +74,11 @@
"abort": {
"not_supported": "Configuration for target rates is not supported at the moment."
}
},
"issues": {
"account_not_found": {
"title": "Account \"{account_id}\" not found",
"description": "The integration failed to retrieve the information associated with your configured account. Please check your account exists and that your API key is valid. Click 'Learn More' to find out how to fix this."
}
}
}
16 changes: 15 additions & 1 deletion tests/integration/api_client/test_get_account.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,18 @@ async def test_when_get_account_is_called_then_electricity_and_gas_points_return
assert len(meter_point["agreements"]) == 1
assert "tariff_code" in meter_point["agreements"][0]
assert "valid_from" in meter_point["agreements"][0]
assert "valid_to" in meter_point["agreements"][0]
assert "valid_to" in meter_point["agreements"][0]

@pytest.mark.asyncio
async def test_when_get_account_is_called_and_not_found_then_none_returned():
# Arrange
context = get_test_context()

client = OctopusEnergyApiClient(context["api_key"])
account_id = "not-an-account"

# Act
account = await client.async_get_account(account_id)

# Assert
assert account is None

0 comments on commit 7c7801a

Please sign in to comment.