Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
1953936
autopep linting
shtrom May 30, 2023
c892456
Switch kWh usage to STATE_CLASS_TOTAL_INCREASING to avoid reset artef…
shtrom May 30, 2023
abf409d
Add per-tariff summary
shtrom May 30, 2023
3a65b81
Use STATE_CLASS_TOTAL with last_reset for everything
shtrom May 31, 2023
713a1ad
Make update asynchronous
shtrom Aug 3, 2023
4a6a71f
Split out AuroraPlus API coordinator
shtrom Aug 3, 2023
2c8e710
Sanitise entity_id
shtrom Aug 3, 2023
069ee6f
Add HistoricalSensor for daily per-tariff statistics #6
shtrom Aug 3, 2023
71c624a
Fix Throttle behaviour
shtrom Aug 9, 2023
0512eff
Cleanup logging
shtrom Aug 9, 2023
52f80a8
Be a bit more defensive on empty data
shtrom Aug 9, 2023
7bbcf57
Update to auroraplus 1.5.0
shtrom Aug 11, 2023
8772b31
Only create entities for available tariffs
shtrom Aug 12, 2023
db56792
Look backwards in time for valid data when updating
shtrom Aug 13, 2023
d912032
Get Tariffs from monthly summary
shtrom Aug 13, 2023
694677a
Add per-tariff daily cost sensors
shtrom Aug 14, 2023
715507e
Try to improve startup reliability and performance
shtrom Aug 14, 2023
6093a04
Saner log verbosity
shtrom Aug 14, 2023
ac7200b
Don't round values in stats
shtrom Aug 14, 2023
4318f93
[REVERTME] Point to AuroraPLus fork
shtrom Aug 13, 2023
0d29630
Use absolute values on hourly measurements
shtrom Aug 15, 2023
7e277e5
Update AuroraPlus branch for MFA
shtrom Nov 28, 2023
dcb9690
Bump homeassistant-historical-sensor to 2.0.0rc5
shtrom Jan 10, 2024
109603d
Add config_flow
shtrom Jun 24, 2024
de1c674
Bump version to 2.0.0 due to breaking changes
shtrom Jul 2, 2024
1e2ea13
bump homeassistant-historical-sensor to 2.0.0rc6
shtrom Jun 21, 2025
cca06d9
Use hass.config_entries.async_forward_entry_setups
shtrom Jun 21, 2025
7edfd28
new version 2.0.1
shtrom Jun 21, 2025
39abc0b
Add link to Access Token-fetching page
shtrom Jul 26, 2025
d4709d1
Raise ConfigEntryAuthFailed on authorisation errors when fetching data
shtrom Aug 1, 2025
e9d01d1
Use id_token to refresh access_token
shtrom Aug 9, 2025
34fda4d
[REVERTME] Point to AuroraPlus branch with refresh support
shtrom Aug 9, 2025
052c351
manifest: add loggers
shtrom Aug 10, 2025
2ef5ce3
Save and restore full token accross refreshes
shtrom Aug 14, 2025
bd0fd26
Improve error handling and logging
shtrom Aug 15, 2025
602c636
Rename class and attributes for clarity
shtrom Aug 16, 2025
40f0082
Handle AuroraPlusAuthenticationError
shtrom Aug 16, 2025
963c1b4
Linting in sensor
shtrom Aug 16, 2025
5fc6dba
Update configentry in async update method
shtrom Aug 16, 2025
90f0a2a
more debug logging tweaks
shtrom Aug 17, 2025
1e3c2a6
Use full token in update_listener if changed
shtrom Aug 17, 2025
9ec98d5
readme: one fewer CAVEAT!
shtrom Aug 17, 2025
28cea81
coordinator: use address from /current rather than /month
shtrom Aug 17, 2025
c5fbb66
api: ensure data has been fetched prior to creating sensors
shtrom Aug 17, 2025
d4eeb67
readme: muliple services not supported
shtrom Aug 17, 2025
8339946
coordinator: get tariffs from previous week
shtrom Aug 18, 2025
5267fe4
readme: maintained for 2025
shtrom Aug 18, 2025
6d5277e
manifest: point AuroraPlus back to upstream and add codeowner
shtrom Aug 25, 2025
c060eb4
lint: ruff check --fix + manual fixes
shtrom Aug 25, 2025
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
70 changes: 54 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
[![buy me a coffee](https://img.shields.io/badge/If%20you%20like%20it-Buy%20us%20a%20coffee-green.svg?style=for-the-badge)](https://www.buymeacoffee.com/leighcurran)
[![hacs_badge](https://img.shields.io/badge/HACS-Custom-orange.svg?style=for-the-badge)](https://github.com/custom-components/hacs)
![Maintenance](https://img.shields.io/maintenance/yes/2022.svg?style=for-the-badge)
![Maintenance](https://img.shields.io/maintenance/yes/2025.svg?style=for-the-badge)

# Aurora+ for Home Assistant

The Aurora+ integration adds support for retriving data from the Aurora+ API such as:
The Aurora+ integration adds support for retrieving data from the Aurora+ API such as:

- EstimatedBalance - This is shown in the Aurora+ app as 'Balance'
- UsageDaysRemaining - This is shown in the Aurora+ app as 'Days Prepaid'
Expand All @@ -16,20 +16,58 @@ The Aurora+ integration adds support for retriving data from the Aurora+ API suc
- NumberOfUnpaidBills
- BillOverDueAmount

It also uses https://github.com/ldotlopez/ha-historical-sensor/ to fetch hourly
usage from the previous day, and make it available for the Energy dashboard:

- Dollar Value Usage (Total and per-Tariff)
- Kilowatt Hour Usage (Total and per-Tariff)

Note: To use the Aurora+ integration you need a valid account with Aurora.

## Configuration
Using *YAML*: add `auroraplus` platform to your sensor configuration in `configuration.yaml`. Example:

```yaml
# Example configuration.yaml entry
sensor:
- platform: auroraplus
name: "Power Sensor"
username: [email protected]
password: Password
scan_interval:
hours: 2
rounding: 1
```
Note: Name, scan_interval and rounding are optional. If scan_interval is not set a default value of 1 hours will be used. If rounding is not set a default value of 2 will be used. Most Aurora+ data is updated daily.

This integration uses Home Assistant's config flow. Simply go to `Settings` /
`Devices & Services`, choose `Add Integration`, and search for `Aurora+`.

In the configuration dialog, you need to input an OAuth access key, which allows
access to your account's data without MFA. Authentication and API access is done
via https://github.com/shtrom/AuroraPlus/tree/oauth-mfa-token, which you can
also use to obtain the ID token.

The easiest way to get a fresh token is to use [this
page](https://shtrom.github.io/AuroraPlus/)). Follow the instructions to login
to AuroraPlus and provide the URL of the error page to obtain an `id_token`
suitable to bootstrap authentication in HA.

If you'd prefer not to trust a random page on the web with your AuroraPlus
credentials, you can also obtain the token locally. On any machine able to run
Python (not necessarily your Home Assistant server), install the AuroraPlus
Python module from the URL above. You can then follow the instructions at
https://github.com/shtrom/AuroraPlus/tree/oauth-mfa-token?tab=readme-ov-file#obtain-a-token.

Essentially, just run

aurora_get_token

and follow the instructions (open link, enter MFA, copy URL of error page back).

## CAVEATs

1. The Aurora+ API doesn't deliver real-time metering, so the data for current
usage will always be unavailable. It only provides updates containing data
for the previous day, in daily batches. Those batches may not be available
until late the next day, leading to a lag of 1 to 2 days in the energy
dashboard.

2. Upon adding the integration, only tariffs with readings on the previous
week will be available to add to the energy dashboard. This could be
an issue if the plan was just changed. Sensors for the new tariffs won't
show up. Simply restart Home Assistant the next week for new sensors to
be created.

3. Upon reauthenticating, a bunch of SQLAlchemyError will prop up in the logs.
They are currently believed to be harmless, and stop happening after a
restart.

4. Support for multiple services is not complete, and would rely on matching
functionality not available in the Python library yet.
46 changes: 45 additions & 1 deletion custom_components/auroraplus/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1,45 @@
"""The auroraplus sensor integration."""
"""The auroraplus sensor integration."""

import logging

from homeassistant.exceptions import (
ConfigEntryNotReady,
PlatformNotReady,
)


from .api import aurora_init
from .const import CONF_TOKEN, CONF_ID_TOKEN, DOMAIN
from .coordinator import AuroraPlusCoordinator

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(hass, entry):
"""Set up entry."""
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = dict(entry.data)

id_token = entry.data.get(CONF_ID_TOKEN)
token = entry.data.get(CONF_TOKEN)

try:
api = await hass.async_add_executor_job(aurora_init, token, id_token)
except OSError as err:
raise PlatformNotReady("Connection to Aurora+ failed") from err

entry.async_on_unload(
entry.add_update_listener(AuroraPlusCoordinator.update_listener)
)

entry.runtime_data = AuroraPlusCoordinator(hass, entry, api)

if not (
hasattr(entry.runtime_data, "week")
and entry.runtime_data.week.get("TariffTypes")
):
raise ConfigEntryNotReady("No tariffs in returned data, yet")

await hass.config_entries.async_forward_entry_setups(entry, ["sensor"])

return True
38 changes: 38 additions & 0 deletions custom_components/auroraplus/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import logging

from auroraplus import AuroraPlusApi, AuroraPlusAuthenticationError
from requests.exceptions import HTTPError

from homeassistant.exceptions import (
ConfigEntryAuthFailed,
)


_LOGGER = logging.getLogger(__name__)


def aurora_init(
token: dict = {},
id_token: str | None = None,
access_token: str | None = None,
):
_LOGGER.debug(f"aurora_init {token=} {id_token=} {access_token=}")
try:
api = AuroraPlusApi(token=token, id_token=id_token, access_token=access_token)

# We need this data in AuroraPlusCoordinator.__init__so we have the
# serviceAgreementID, preiseAddress, and tariffs over the previous
# week however HomeAssistant is not happy if the calls are made there.

api.get_info()
api.getweek()

except AuroraPlusAuthenticationError as e:
raise ConfigEntryAuthFailed("authentication failure on init") from e
except HTTPError as e:
status_code = e.response.status_code
if status_code in [401, 403]:
raise ConfigEntryAuthFailed("authentication failure on init") from e
raise e

return api
103 changes: 102 additions & 1 deletion custom_components/auroraplus/config_flow.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,109 @@
import logging
from typing import Any

import homeassistant.helpers.config_validation as cv
from homeassistant import config_entries
from .const import DOMAIN
from homeassistant.const import (
CONF_ACCESS_TOKEN,
)
from homeassistant.exceptions import ConfigEntryAuthFailed


import voluptuous as vol

from .api import aurora_init
from .const import (
CONF_ID_TOKEN,
CONF_SERVICE_AGREEMENT_ID,
CONF_TOKEN,
DOMAIN,
)

_LOGGER = logging.getLogger(__name__)

# PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
AUTH_SCHEMA = vol.Schema(
{
vol.Required(CONF_ID_TOKEN): cv.string,
}
)


class AuroraPlusConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""AuroraPlus config flow."""

VERSION = 0
MINOR_VERSION = 1
reauth_entry = None

async def async_step_user(self, user_input: dict[str, Any] | None = None):
return await self._configure(user_input)

async def _configure(self, user_input: dict[str, Any] | None = None):
"""
Get id_token from the user, and create a new service.

If self.reauth_entry is set, this entry will be updated instead.

"""
errors = {}
if user_input is not None:
id_token = user_input.get(CONF_ID_TOKEN)
try:
api = await self.hass.async_add_executor_job(aurora_init, {}, id_token)
address = api.premiseAddress
await self.async_set_unique_id(api.serviceAgreementID)

if self.reauth_entry:
self.hass.config_entries.async_update_entry(
self.reauth_entry,
data={
CONF_ACCESS_TOKEN: None,
CONF_ID_TOKEN: id_token,
CONF_SERVICE_AGREEMENT_ID: api.serviceAgreementID,
CONF_TOKEN: None,
},
)
await self.hass.config_entries.async_reload(
self.reauth_entry.entry_id
)
return self.async_abort(reason="reauth_successful")

else:
return self.async_create_entry(
title=address,
data={
CONF_ACCESS_TOKEN: None,
CONF_ID_TOKEN: id_token,
CONF_SERVICE_AGREEMENT_ID: api.serviceAgreementID,
CONF_TOKEN: None,
},
)

except ConfigEntryAuthFailed:
errors = {
"base": "auth",
}

return self.async_show_form(
step_id="user",
data_schema=AUTH_SCHEMA,
errors=errors,
)

async def async_step_reauth(self, user_input=None):
"""Perform reauth upon an API authentication error."""
self.reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
return await self.async_step_reauth_confirm()

async def async_step_reauth_confirm(self, user_input=None):
"""Dialog that informs the user that reauth is required."""
if user_input is None:
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({}),
)

return await self.async_step_user()
28 changes: 28 additions & 0 deletions custom_components/auroraplus/const.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import datetime

DOMAIN = "auroraplus"

CONF_TOKEN = "token"
CONF_ID_TOKEN = "id_token"
CONF_SERVICE_AGREEMENT_ID = "service_agreement_id"
CONF_ROUNDING = "rounding"

SENSOR_ESTIMATEDBALANCE = 'Estimated Balance'
SENSOR_DOLLARVALUEUSAGE = 'Dollar Value Usage'
SENSOR_KILOWATTHOURUSAGE = 'Kilowatt Hour Usage'
SENSOR_KILOWATTHOURUSAGETARIFF = 'Kilowatt Hour Usage Tariff'
SENSOR_DOLLARVALUEUSAGETARIFF = 'Dollar Value Usage Tariff'

SENSORS_MONETARY = [
SENSOR_ESTIMATEDBALANCE,
SENSOR_DOLLARVALUEUSAGE,
]


POSSIBLE_MONITORED = SENSORS_MONETARY + [SENSOR_KILOWATTHOURUSAGE]

DEFAULT_MONITORED = POSSIBLE_MONITORED

DEFAULT_ROUNDING = 2
DEFAULT_SCAN_INTERVAL = datetime.timedelta(hours=1)

Loading