Skip to content

Commit

Permalink
New release (#877)
Browse files Browse the repository at this point in the history
  • Loading branch information
BottlecapDave authored May 18, 2024
2 parents d9505eb + 89b3f22 commit 15c267c
Show file tree
Hide file tree
Showing 16 changed files with 143 additions and 32 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* Fixed warning for deprecated EventType reference ([e8786df](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/e8786df9fb476a56fd4a4347f03ab46075c02be7))
* Limited backoff logic to 30 minutes between attempts so it doesn't increase forever ([f2de64d](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/f2de64d2ac176c26e7224b6ddeae04cf5f85d6ea))
* Updated attributes and events to have datetimes in local time (UTC in winter, BST in summer) ([19ac60f](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/commit/19ac60f10ae81f4a81bedbd448c5563c41e35805))
* Updated minimum Home Assistant version to 2024.5.0


### Features
Expand Down
Binary file added _docs/assets/beta-hacs-beta-toggle.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added _docs/assets/beta-hacs-redownload.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions _docs/community.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ If you're wanting to display upcoming prices in a nice readable format, then mig

<img src="https://github.com/lozzd/octopus-energy-rates-card/raw/main/assets/screenshot_1.png" height="300"/>

## Home battery and prediction and charging

If you use OE with a home battery, you might be interested in [predbat](https://github.com/springfall2008/batpred), which can take advantage of the data available through this integration.

## Import and Export Rates Charts

Thanks to @fboundy you can use [ApexCharts Card](https://github.com/RomRider/apexcharts-card) to plot the import and export rates for the current day using the following configuration.
Expand Down
24 changes: 23 additions & 1 deletion _docs/faq.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,14 +128,20 @@ Enabling entities is easy. All you need to do is
5. Click on the `enable` button or toggle the `Enabled` toggle to on
6. Click on `update`

## I have entities that are missing
## I have entities that are missing. Is the integration broken?

The integration only looks at the first property associated with your account that doesn't have a moved out date attached to it. If you are still missing entities, follow the instructions to [increase the logs](#how-do-i-increase-the-logs-for-the-integration).

You should then see entries associated with this component stating either entities were added, skipped or no entities were available at all.

The identifiers of the entities should then be checked against your Octopus Energy dashboard to verify the correct entities are being picked up. If this is producing unexpected results, then you should raise an issue.

## I have meters that are missing. Is the integration broken?

The integration will only surface entities associated with meters in your first active property. Each meter must also have an active tariff associated with it.

If you [follow the instructions](#ive-been-asked-for-my-meter-information-in-a-bug-request-how-do-i-obtain-this) to download diagnostics, you can see all agreements associated with each of your meters. You will need an agreement with a start date in the past and an end date either set to `null` or in the future for the meter to be picked up by the integration.

## I'm an agile user and having trouble setting up a target rate sensor. What am I doing wrong?

Rate data for agile tariffs are not available in full for the next day, which can cause issues with target rate sensors in their default state. We prevent you from setting up target rate sensors in this form. More information around this can be found in the [target rate documentation](./setup/target_rate.md#agile-users).
Expand Down Expand Up @@ -179,6 +185,22 @@ If you've installed via HACS, then you can keep an eye on `sensor.hacs` to see t

If you've installed the integration manually, then you should keep an eye on the [GitHub releases](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/releases). You could even subscribe to the [RSS feed](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/releases.atom).

## There's a beta release of the integration that I would like to take part in, how do I do this?

If you install the integration manually, it's just a case of getting the source of the [beta release](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/releases), replacing the old installation with the new one and restarting Home Assistant.

If you install the integration via HACS, then you will need to

* Go to [HACS](https://my.home-assistant.io/redirect/hacs_repository/?owner=BottlecapDave&repository=homeassistant-octopusenergy&category=integration), click on the three dots and then click redownload

![Redownload screen in HACS](./assets/beta-hacs-redownload.png)

* Toggle on `Show beta versions` and select the target beta. Once selected, click `Download`.

![Beta toggle in HACS](./assets/beta-hacs-beta-toggle.png)

* Once downloaded, you'll need to restart Home Assistant for the new version to take effect.

## How do I increase the logs for the integration?

If you are having issues, it would be helpful to include Home Assistant logs as part of any raised issue. This can be done by following the [instructions](https://www.home-assistant.io/docs/configuration/troubleshooting/#enabling-debug-logging) outlined by Home Assistant.
Expand Down
14 changes: 9 additions & 5 deletions custom_components/octopus_energy/config/target_rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,21 @@ def validate_target_rate_config(data, account_info, now):
if data[CONFIG_TARGET_HOURS] % 0.5 != 0:
errors[CONFIG_TARGET_HOURS] = "invalid_target_hours"

if CONFIG_TARGET_START_TIME in data:
if CONFIG_TARGET_HOURS not in errors:
if data[CONFIG_TARGET_HOURS] < 0.5:
errors[CONFIG_TARGET_HOURS] = "invalid_target_hours"

if CONFIG_TARGET_START_TIME in data and data[CONFIG_TARGET_START_TIME] is not None:
matches = re.search(REGEX_TIME, data[CONFIG_TARGET_START_TIME])
if matches is None:
errors[CONFIG_TARGET_START_TIME] = "invalid_target_time"

if CONFIG_TARGET_END_TIME in data:
if CONFIG_TARGET_END_TIME in data and data[CONFIG_TARGET_END_TIME] is not None:
matches = re.search(REGEX_TIME, data[CONFIG_TARGET_END_TIME])
if matches is None:
errors[CONFIG_TARGET_END_TIME] = "invalid_target_time"

if CONFIG_TARGET_OFFSET in data:
if CONFIG_TARGET_OFFSET in data and data[CONFIG_TARGET_OFFSET] is not None:
matches = re.search(REGEX_OFFSET_PARTS, data[CONFIG_TARGET_OFFSET])
if matches is None:
errors[CONFIG_TARGET_OFFSET] = "invalid_offset"
Expand Down Expand Up @@ -193,8 +197,8 @@ def validate_target_rate_config(data, account_info, now):
if data[CONFIG_TARGET_TYPE] != CONFIG_TARGET_TYPE_CONTINUOUS:
errors[CONFIG_TARGET_WEIGHTING] = "weighting_not_supported"

start_time = data[CONFIG_TARGET_START_TIME] if CONFIG_TARGET_START_TIME in data else "00:00"
end_time = data[CONFIG_TARGET_END_TIME] if CONFIG_TARGET_END_TIME in data else "00:00"
start_time = data[CONFIG_TARGET_START_TIME] if CONFIG_TARGET_START_TIME in data and data[CONFIG_TARGET_START_TIME] is not None else "00:00"
end_time = data[CONFIG_TARGET_END_TIME] if CONFIG_TARGET_END_TIME in data and data[CONFIG_TARGET_END_TIME] is not None else "00:00"

is_time_valid = CONFIG_TARGET_START_TIME not in errors and CONFIG_TARGET_END_TIME not in errors

Expand Down
21 changes: 6 additions & 15 deletions custom_components/octopus_energy/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -356,18 +356,6 @@ async def __async_setup_target_rate_schema__(self, config, errors):
if (CONFIG_TARGET_MPAN not in config):
config[CONFIG_TARGET_MPAN] = meters[0]

start_time_key = vol.Optional(CONFIG_TARGET_START_TIME)
if (CONFIG_TARGET_START_TIME in config):
start_time_key = vol.Optional(CONFIG_TARGET_START_TIME, default=config[CONFIG_TARGET_START_TIME])

end_time_key = vol.Optional(CONFIG_TARGET_END_TIME)
if (CONFIG_TARGET_END_TIME in config):
end_time_key = vol.Optional(CONFIG_TARGET_END_TIME, default=config[CONFIG_TARGET_END_TIME])

offset_key = vol.Optional(CONFIG_TARGET_OFFSET)
if (CONFIG_TARGET_OFFSET in config):
offset_key = vol.Optional(CONFIG_TARGET_OFFSET, default=config[CONFIG_TARGET_OFFSET])

# True by default for backwards compatibility
is_rolling_target = True
if (CONFIG_TARGET_ROLLING_TARGET in config):
Expand Down Expand Up @@ -402,9 +390,9 @@ async def __async_setup_target_rate_schema__(self, config, errors):
mode=selector.SelectSelectorMode.DROPDOWN,
)
),
start_time_key: str,
end_time_key: str,
offset_key: str,
vol.Optional(CONFIG_TARGET_START_TIME): str,
vol.Optional(CONFIG_TARGET_END_TIME): str,
vol.Optional(CONFIG_TARGET_OFFSET): str,
vol.Optional(CONFIG_TARGET_ROLLING_TARGET): bool,
vol.Optional(CONFIG_TARGET_LAST_RATES): bool,
vol.Optional(CONFIG_TARGET_INVERT_TARGET_RATES): bool,
Expand All @@ -417,6 +405,9 @@ async def __async_setup_target_rate_schema__(self, config, errors):
CONFIG_TARGET_HOURS: f'{config[CONFIG_TARGET_HOURS]}',
CONFIG_TARGET_TYPE: config[CONFIG_TARGET_TYPE],
CONFIG_TARGET_MPAN: config[CONFIG_TARGET_MPAN],
CONFIG_TARGET_START_TIME: config[CONFIG_TARGET_START_TIME] if CONFIG_TARGET_START_TIME in config else None,
CONFIG_TARGET_END_TIME: config[CONFIG_TARGET_END_TIME] if CONFIG_TARGET_END_TIME in config else None,
CONFIG_TARGET_OFFSET: config[CONFIG_TARGET_OFFSET] if CONFIG_TARGET_OFFSET in config else None,
CONFIG_TARGET_ROLLING_TARGET: is_rolling_target,
CONFIG_TARGET_LAST_RATES: find_last_rates,
CONFIG_TARGET_INVERT_TARGET_RATES: invert_target_rates,
Expand Down
16 changes: 16 additions & 0 deletions custom_components/octopus_energy/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,22 @@
CONFIG_TARGET_MAX_RATE = "maximum_rate"
CONFIG_TARGET_WEIGHTING = "weighting"

CONFIG_TARGET_KEYS = [
CONFIG_TARGET_NAME,
CONFIG_TARGET_HOURS,
CONFIG_TARGET_TYPE,
CONFIG_TARGET_START_TIME,
CONFIG_TARGET_END_TIME,
CONFIG_TARGET_MPAN,
CONFIG_TARGET_OFFSET,
CONFIG_TARGET_ROLLING_TARGET,
CONFIG_TARGET_LAST_RATES,
CONFIG_TARGET_INVERT_TARGET_RATES,
CONFIG_TARGET_MIN_RATE,
CONFIG_TARGET_MAX_RATE,
CONFIG_TARGET_WEIGHTING
]

CONFIG_COST_NAME = "name"
CONFIG_COST_MPAN = "mpan"
CONFIG_COST_TARGET_ENTITY_ID = "target_entity_id"
Expand Down
3 changes: 2 additions & 1 deletion custom_components/octopus_energy/electricity/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ def __init__(self, hass: HomeAssistant, meter, point):

self.entity_id = generate_entity_id("sensor.{}", self.unique_id, hass=hass)

export_name_suffix = " Export" if self._is_export == True else ""
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, f"electricity_{self._serial_number}_{self._mpan}")},
name=f"Electricity Meter{self._export_name_addition}",
name=f"Electricity Meter{export_name_suffix}",
connections=set(),
manufacturer=self._meter["manufacturer"],
model=self._meter["model"],
Expand Down
3 changes: 0 additions & 3 deletions custom_components/octopus_energy/statistics/refresh.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,6 @@ async def async_refresh_previous_gas_consumption_data(
rates,
UnitOfVolume.CUBIC_METERS,
"consumption_m3",
False,
initial_statistics=previous_m3_consumption_result
)

Expand All @@ -183,7 +182,6 @@ async def async_refresh_previous_gas_consumption_data(
rates,
UnitOfEnergy.KILO_WATT_HOUR,
"consumption_kwh",
False,
initial_statistics=previous_kwh_consumption_result
)

Expand All @@ -196,7 +194,6 @@ async def async_refresh_previous_gas_consumption_data(
rates,
"GBP",
"consumption_kwh",
False,
previous_cost_result
)

Expand Down
18 changes: 15 additions & 3 deletions custom_components/octopus_energy/target_rates/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from homeassistant.util.dt import (as_utc, parse_datetime)

from ..utils.conversions import value_inc_vat_to_pounds
from ..const import REGEX_OFFSET_PARTS, REGEX_WEIGHTING
from ..const import CONFIG_TARGET_KEYS, REGEX_OFFSET_PARTS, REGEX_WEIGHTING

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -296,7 +296,7 @@ def get_target_rate_info(current_date: datetime, applicable_rates, offset: str =
}

def create_weighting(config: str, number_of_slots: int):
if config is None or config == "":
if config is None or config == "" or config.isspace():
weighting = []
for index in range(number_of_slots):
weighting.append(1)
Expand All @@ -321,4 +321,16 @@ def create_weighting(config: str, number_of_slots: int):

weighting.append(int(parts[index]))

return weighting
return weighting

def compare_config(current_config: dict, existing_config: dict):
if current_config is None or existing_config is None:
return False

for key in CONFIG_TARGET_KEYS:
if ((key not in existing_config and key in current_config) or
(key in existing_config and key not in current_config) or
(key in existing_config and key in current_config and current_config[key] != existing_config[key])):
return False

return True
9 changes: 7 additions & 2 deletions custom_components/octopus_energy/target_rates/target_rate.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from . import (
calculate_continuous_times,
calculate_intermittent_times,
compare_config,
create_weighting,
get_applicable_rates,
get_target_rate_info
Expand Down Expand Up @@ -262,8 +263,12 @@ async def async_added_to_hass(self):
state.attributes,
[CONFIG_TARGET_OLD_NAME, CONFIG_TARGET_OLD_HOURS, CONFIG_TARGET_OLD_TYPE, CONFIG_TARGET_OLD_START_TIME, CONFIG_TARGET_OLD_END_TIME, CONFIG_TARGET_OLD_MPAN]
)
# Make sure our attributes don't override any changed settings
self._attributes.update(self._config)

# Reset everything if our settings have changed
if compare_config(self._config, self._attributes) == False:
self._state = False
self._attributes = self._config.copy()
self._attributes["is_target_export"] = self._is_export

_LOGGER.debug(f'Restored OctopusEnergyTargetRate state: {self._state}')

Expand Down
4 changes: 2 additions & 2 deletions custom_components/octopus_energy/translations/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@
"server_error": "Failed to connect to OE servers. Please try again later",
"account_not_found": "Invalid API key or account id specified",
"value_greater_than_zero": "Value must be greater or equal to 1",
"invalid_target_hours": "Target hours must be in half hour increments (e.g. 0.5 = 30 minutes; 1 = 60 minutes).",
"invalid_target_hours": "Target hours must be in half hour increments (e.g. 0.5 = 30 minutes; 1 = 60 minutes) and a minimum of 0.5.",
"invalid_target_name": "Name must only include lower case alpha characters and underscore (e.g. my_target)",
"invalid_target_time": "Must be in the format HH:MM",
"invalid_offset": "Offset must be in the form of HH:MM:SS with an optional negative symbol",
Expand Down Expand Up @@ -143,7 +143,7 @@
"server_error": "Failed to connect to OE servers. Please try again later",
"account_not_found": "Invalid API key or account id specified",
"value_greater_than_zero": "Value must be greater or equal to 1",
"invalid_target_hours": "Target hours must be in half hour increments (e.g. 0.5 = 30 minutes; 1 = 60 minutes).",
"invalid_target_hours": "Target hours must be in half hour increments (e.g. 0.5 = 30 minutes; 1 = 60 minutes) and a minimum of 0.5.",
"invalid_target_time": "Must be in the format HH:MM",
"invalid_offset": "Offset must be in the form of HH:MM:SS with an optional negative symbol",
"invalid_hours_time_frame": "The target hours do not fit in the elected target time frame",
Expand Down
34 changes: 34 additions & 0 deletions tests/unit/config/test_validate_target_rate_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,38 @@ async def test_when_config_is_valid_no_errors_returned():
assert CONFIG_TARGET_MAX_RATE not in errors
assert CONFIG_TARGET_WEIGHTING not in errors

@pytest.mark.asyncio
async def test_when_optional_config_is_valid_no_errors_returned():
# Arrange
data = {
CONFIG_TARGET_TYPE: CONFIG_TARGET_TYPE_CONTINUOUS,
CONFIG_TARGET_NAME: "test",
CONFIG_TARGET_MPAN: mpan,
CONFIG_TARGET_HOURS: "1.5",
CONFIG_TARGET_START_TIME: None,
CONFIG_TARGET_END_TIME: None,
CONFIG_TARGET_OFFSET: None,
CONFIG_TARGET_MIN_RATE: None,
CONFIG_TARGET_MAX_RATE: None,
CONFIG_TARGET_WEIGHTING: None
}

account_info = get_account_info()

# Act
errors = validate_target_rate_config(data, account_info, now)

# Assert
assert CONFIG_TARGET_NAME not in errors
assert CONFIG_TARGET_MPAN not in errors
assert CONFIG_TARGET_HOURS not in errors
assert CONFIG_TARGET_START_TIME not in errors
assert CONFIG_TARGET_END_TIME not in errors
assert CONFIG_TARGET_OFFSET not in errors
assert CONFIG_TARGET_MIN_RATE not in errors
assert CONFIG_TARGET_MAX_RATE not in errors
assert CONFIG_TARGET_WEIGHTING not in errors

@pytest.mark.asyncio
@pytest.mark.parametrize("name,tariff",[
("", non_agile_tariff),
Expand Down Expand Up @@ -101,13 +133,15 @@ async def test_when_config_has_invalid_name_then_errors_returned(name, tariff):
@pytest.mark.asyncio
@pytest.mark.parametrize("hours,tariff",[
("", non_agile_tariff),
("0", non_agile_tariff),
("-1.0", non_agile_tariff),
("s", non_agile_tariff),
("1.01", non_agile_tariff),
("1.49", non_agile_tariff),
("1.51", non_agile_tariff),
("1.99", non_agile_tariff),
("", agile_tariff),
("0", agile_tariff),
("-1.0", agile_tariff),
("s", agile_tariff),
("1.01", agile_tariff),
Expand Down
23 changes: 23 additions & 0 deletions tests/unit/target_rates/test_compare_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import pytest

from custom_components.octopus_energy.target_rates import compare_config
from custom_components.octopus_energy.const import CONFIG_TARGET_HOURS, CONFIG_TARGET_TYPE

@pytest.mark.asyncio
@pytest.mark.parametrize("existing_config,expected_result",[
(None, False),
({}, False),
({ CONFIG_TARGET_HOURS: 1 }, False),
({ CONFIG_TARGET_HOURS: 2, CONFIG_TARGET_TYPE: "Continuous" }, False),
({ CONFIG_TARGET_HOURS: 1, CONFIG_TARGET_TYPE: "Intermittent" }, False),
({ CONFIG_TARGET_HOURS: 1, CONFIG_TARGET_TYPE: "Continuous" }, True),
({ CONFIG_TARGET_HOURS: 1, CONFIG_TARGET_TYPE: "Continuous", "Something": "else" }, True),
])
async def test_when_config_is_compared_then_expected_value_is_returned(existing_config, expected_result):
current_config = {
CONFIG_TARGET_HOURS: 1,
CONFIG_TARGET_TYPE: "Continuous"
}

actual_result = compare_config(current_config, existing_config)
assert actual_result == expected_result
1 change: 1 addition & 0 deletions tests/unit/target_rates/test_create_weighting.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@pytest.mark.parametrize("config,number_of_slots,expected_weighting",[
(None, 3, [1,1,1]),
("", 3, [1,1,1]),
(" ", 3, [1,1,1]),
("1", 3, [1]),
("1,2,3", 3, [1,2,3]),
("2,*,3", 4, [2,1,1,3]),
Expand Down

0 comments on commit 15c267c

Please sign in to comment.