Skip to content

Commit

Permalink
feat: Added support for business tariffs (5 hours dev time)
Browse files Browse the repository at this point in the history
  • Loading branch information
BottlecapDave committed Jun 2, 2024
1 parent efa8d98 commit 804cd9d
Show file tree
Hide file tree
Showing 54 changed files with 497 additions and 376 deletions.
2 changes: 1 addition & 1 deletion _docs/entities/electricity.md
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ The total cost reported by the meter for the current day during peak hours (the

You may be on an existing tariff but want to know if the grass is greener (or cheaper) on the other side. The following entities are available in a disabled state, which when enabled can give you an indication what you'd be paying if you were on a different tariff and didn't change your energy habits.

See [below](#previous-accumulative-cost-override-tariff-electricity) for instructions on how to set up.
See [below](#previous-accumulative-cost-override-tariff) for instructions on how to set up.

!!! info

Expand Down
4 changes: 2 additions & 2 deletions _docs/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ This is fired when the next day rates are updated.

`octopus_energy_gas_previous_consumption_rates`

This is fired when the [previous consumption's](./entities/gas.md#previous-accumulative-consumption) rates are updated.
This is fired when the [previous consumption's](./entities/gas.md#previous-accumulative-consumption-m3) rates are updated.

| Attribute | Type | Description |
|-----------|------|-------------|
Expand Down Expand Up @@ -315,7 +315,7 @@ This event is raised when a new saving session is discovered.
| Attribute | Type | Description |
|-----------|------|-------------|
| `account_id` | `string` | The id of the account the new saving session is for |
| `event_code` | `string` | The code of the new saving session event. This is required if you wishing to use the [join event service](./services.md#join_octoplus_saving_session_event) |
| `event_code` | `string` | The code of the new saving session event. This is required if you wishing to use the [join event service](./services.md#octopus_energyjoin_octoplus_saving_session_event) |
| `event_id` | `string` | The id of the event |
| `event_start` | `datetime` | The date/time the event starts |
| `event_end` | `datetime` | The date/time the event ends |
Expand Down
3 changes: 3 additions & 0 deletions _docs/repairs/unknown_product.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Repairs - Unknown product

If you receive this error around one or more of your tariffs, it's because the Octopus Energy API can not find the product associated with your tariff. This usually occurs if you're on a tariff which is currently internal and has not been exposed. In this scenario, you should [raise an issue](https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/issues) with the tariff code and your meter information, which can be obtained by following the [FAQ](../faq.md#ive-been-asked-for-my-meter-information-in-a-bug-request-how-do-i-obtain-this).
3 changes: 0 additions & 3 deletions _docs/repairs/unknown_tariff.md

This file was deleted.

3 changes: 0 additions & 3 deletions _docs/repairs/unknown_tariff_format.md

This file was deleted.

30 changes: 15 additions & 15 deletions custom_components/octopus_energy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@
from .coordinators.saving_sessions import async_setup_saving_sessions_coordinators
from .coordinators.greenness_forecast import async_setup_greenness_forecast_coordinator
from .statistics import get_statistic_ids_to_remove
from .intelligent import async_mock_intelligent_data, get_intelligent_features, is_intelligent_tariff, mock_intelligent_device
from .intelligent import async_mock_intelligent_data, get_intelligent_features, is_intelligent_product, mock_intelligent_device

from .config.main import async_migrate_main_config
from .config.target_rates import async_migrate_target_config
from .utils import get_active_tariff_code
from .utils import get_active_tariff
from .utils.tariff_overrides import async_get_tariff_override

from .const import (
Expand Down Expand Up @@ -132,8 +132,8 @@ async def async_close_connection(_) -> None:
account_info = account_result.account if account_result is not None else None
for point in account_info["electricity_meter_points"]:
# We only care about points that have active agreements
electricity_tariff_code = get_active_tariff_code(now, point["agreements"])
if electricity_tariff_code is not None:
electricity_tariff = get_active_tariff(now, point["agreements"])
if electricity_tariff is not None:
for meter in point["meters"]:
mpan = point["mpan"]
serial_number = meter["serial_number"]
Expand All @@ -151,8 +151,8 @@ async def async_close_connection(_) -> None:
account_info = account_result.account if account_result is not None else None
for point in account_info["electricity_meter_points"]:
# We only care about points that have active agreements
electricity_tariff_code = get_active_tariff_code(now, point["agreements"])
if electricity_tariff_code is not None:
electricity_tariff = get_active_tariff(now, point["agreements"])
if electricity_tariff is not None:
for meter in point["meters"]:
mpan = point["mpan"]
serial_number = meter["serial_number"]
Expand Down Expand Up @@ -219,8 +219,8 @@ async def async_setup_dependencies(hass, config):
for meter in point["meters"]:
serial_number = meter["serial_number"]

tariff_code = get_active_tariff_code(now, point["agreements"])
if tariff_code is None:
tariff = get_active_tariff(now, point["agreements"])
if tariff is None:
gas_device = device_registry.async_get_device(identifiers={(DOMAIN, f"gas_{serial_number}_{mprn}")})
if gas_device is not None:
_LOGGER.debug(f'Removed gas device {serial_number}/{mprn} due to no active tariff')
Expand All @@ -236,14 +236,14 @@ async def async_setup_dependencies(hass, config):
intelligent_serial_number = None
for point in account_info["electricity_meter_points"]:
mpan = point["mpan"]
electricity_tariff_code = get_active_tariff_code(now, point["agreements"])
electricity_tariff = get_active_tariff(now, point["agreements"])

for meter in point["meters"]:
serial_number = meter["serial_number"]

if electricity_tariff_code is not None:
if electricity_tariff is not None:
if meter["is_export"] == False:
if is_intelligent_tariff(electricity_tariff_code):
if is_intelligent_product(electricity_tariff.product):
intelligent_mpan = mpan
intelligent_serial_number = serial_number
has_intelligent_tariff = True
Expand All @@ -257,8 +257,8 @@ async def async_setup_dependencies(hass, config):
if should_mock_intelligent_data:
# Pick the first meter if we're mocking our intelligent data
for point in account_info["electricity_meter_points"]:
tariff_code = get_active_tariff_code(now, point["agreements"])
if tariff_code is not None:
tariff = get_active_tariff(now, point["agreements"])
if tariff is not None:
for meter in point["meters"]:
intelligent_mpan = point["mpan"]
intelligent_serial_number = meter["serial_number"]
Expand All @@ -280,8 +280,8 @@ async def async_setup_dependencies(hass, config):

for point in account_info["electricity_meter_points"]:
# We only care about points that have active agreements
electricity_tariff_code = get_active_tariff_code(now, point["agreements"])
if electricity_tariff_code is not None:
electricity_tariff = get_active_tariff(now, point["agreements"])
if electricity_tariff is not None:
for meter in point["meters"]:
mpan = point["mpan"]
serial_number = meter["serial_number"]
Expand Down
43 changes: 9 additions & 34 deletions custom_components/octopus_energy/api_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@
from ..const import INTEGRATION_VERSION

from ..utils import (
get_tariff_parts,
is_day_night_tariff,
)


from .intelligent_device import IntelligentDevice
from .octoplus import RedeemOctoplusPointsResponse
from .intelligent_settings import IntelligentSettings
Expand Down Expand Up @@ -774,20 +773,14 @@ async def async_get_electricity_day_night_rates(self, product_code, tariff_code,

return results

async def async_get_electricity_rates(self, tariff_code: str, is_smart_meter: bool, period_from: datetime, period_to: datetime):
async def async_get_electricity_rates(self, product_code: str, tariff_code: str, is_smart_meter: bool, period_from: datetime, period_to: datetime):
"""Get the current rates"""

tariff_parts = get_tariff_parts(tariff_code)
if tariff_parts is None:
return None

product_code = tariff_parts.product_code

if (tariff_parts.rate.startswith("1")):
return await self.async_get_electricity_standard_rates(product_code, tariff_code, period_from, period_to)
else:
if is_day_night_tariff(tariff_code):
return await self.async_get_electricity_day_night_rates(product_code, tariff_code, is_smart_meter, period_from, period_to)

else:
return await self.async_get_electricity_standard_rates(product_code, tariff_code, period_from, period_to)

async def async_get_electricity_consumption(self, mpan, serial_number, period_from, period_to, page_size: int | None = None):
"""Get the current electricity consumption"""

Expand Down Expand Up @@ -831,14 +824,8 @@ async def async_get_electricity_consumption(self, mpan, serial_number, period_fr
_LOGGER.warning(f'Failed to connect. Timeout of {self._timeout} exceeded.')
raise TimeoutException()

async def async_get_gas_rates(self, tariff_code, period_from, period_to):
async def async_get_gas_rates(self, product_code: str, tariff_code: str, period_from, period_to):
"""Get the gas rates"""
tariff_parts = get_tariff_parts(tariff_code)
if tariff_parts is None:
return None

product_code = tariff_parts.product_code

results = []

try:
Expand Down Expand Up @@ -912,14 +899,8 @@ async def async_get_product(self, product_code):
_LOGGER.warning(f'Failed to connect. Timeout of {self._timeout} exceeded.')
raise TimeoutException()

async def async_get_electricity_standing_charge(self, tariff_code, period_from, period_to):
async def async_get_electricity_standing_charge(self, product_code, tariff_code, period_from, period_to):
"""Get the electricity standing charges"""
tariff_parts = get_tariff_parts(tariff_code)
if tariff_parts is None:
return None

product_code = tariff_parts.product_code

result = None

try:
Expand All @@ -940,14 +921,8 @@ async def async_get_electricity_standing_charge(self, tariff_code, period_from,
_LOGGER.warning(f'Failed to connect. Timeout of {self._timeout} exceeded.')
raise TimeoutException()

async def async_get_gas_standing_charge(self, tariff_code, period_from, period_to):
async def async_get_gas_standing_charge(self, product_code, tariff_code, period_from, period_to):
"""Get the gas standing charges"""
tariff_parts = get_tariff_parts(tariff_code)
if tariff_parts is None:
return None

product_code = tariff_parts.product_code

result = None

try:
Expand Down
6 changes: 3 additions & 3 deletions custom_components/octopus_energy/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from .target_rates.target_rate import OctopusEnergyTargetRate
from .intelligent.dispatching import OctopusEnergyIntelligentDispatching
from .greenness_forecast.highlighted import OctopusEnergyGreennessForecastHighlighted
from .utils import get_active_tariff_code
from .utils import get_active_tariff
from .intelligent import get_intelligent_features
from .api_client.intelligent_device import IntelligentDevice

Expand Down Expand Up @@ -91,7 +91,7 @@ async def async_setup_main_sensors(hass, entry, async_add_entities):

for point in account_info["electricity_meter_points"]:
# We only care about points that have active agreements
tariff_code = get_active_tariff_code(now, point["agreements"])
tariff_code = get_active_tariff(now, point["agreements"])
if tariff_code is not None:
for meter in point["meters"]:
mpan = point["mpan"]
Expand Down Expand Up @@ -128,7 +128,7 @@ async def async_setup_target_sensors(hass, entry, async_add_entities):
now = utcnow()
is_export = False
for point in account_info["electricity_meter_points"]:
tariff_code = get_active_tariff_code(now, point["agreements"])
tariff_code = get_active_tariff(now, point["agreements"])
if tariff_code is not None:
# For backwards compatibility, pick the first applicable meter
if point["mpan"] == mpan or mpan is None:
Expand Down
10 changes: 5 additions & 5 deletions custom_components/octopus_energy/config/__init__.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from ..utils import get_active_tariff_code
from ..utils import Tariff, get_active_tariff

def get_meter_tariffs(account_info, now):
def get_meter_tariffs(account_info, now) -> "dict[str, Tariff]":
meters = {}
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 is not None:
meters[point["mpan"]] = active_tariff_code
active_tariff = get_active_tariff(now, point["agreements"])
if active_tariff is not None:
meters[point["mpan"]] = active_tariff

return meters
2 changes: 1 addition & 1 deletion custom_components/octopus_energy/config/target_rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ def validate_target_rate_config(data, account_info, now):
errors[CONFIG_TARGET_MPAN] = "invalid_mpan"
elif is_time_valid:
tariff = meter_tariffs[data[CONFIG_TARGET_MPAN]]
if is_agile_tariff(tariff):
if is_agile_tariff(tariff.code):
if is_in_agile_darkzone(start_time, end_time):
errors[CONFIG_TARGET_END_TIME] = "invalid_end_time_agile"

Expand Down
6 changes: 3 additions & 3 deletions custom_components/octopus_energy/config_flow.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,23 +62,23 @@
DATA_SCHEMA_ACCOUNT,
)

from .utils import get_active_tariff_code
from .utils import get_active_tariff

_LOGGER = logging.getLogger(__name__)

def get_target_rate_meters(account_info, now):
meters = []
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"])
active_tariff = get_active_tariff(now, point["agreements"])

is_export = False
for meter in point["meters"]:
if meter["is_export"] == True:
is_export = True
break

if active_tariff_code is not None:
if active_tariff is not None:
meters.append(selector.SelectOptionDict(value=point["mpan"], label= f'{point["mpan"]} ({"Export" if is_export == True else "Import"})'))

return meters
Expand Down
Loading

0 comments on commit 804cd9d

Please sign in to comment.