Skip to content

Commit

Permalink
feat: Updated standing charge coordinator to focus on single meter
Browse files Browse the repository at this point in the history
  • Loading branch information
BottlecapDave committed Oct 1, 2023
1 parent 682b202 commit 8524005
Show file tree
Hide file tree
Showing 13 changed files with 320 additions and 58 deletions.
3 changes: 1 addition & 2 deletions custom_components/octopus_energy/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,7 @@
DATA_INTELLIGENT_SETTINGS = "INTELLIGENT_SETTINGS"
DATA_INTELLIGENT_SETTINGS_COORDINATOR = "INTELLIGENT_SETTINGS_COORDINATOR"

DATA_ELECTRICITY_STANDING_CHARGES_COORDINATOR = "ELECTRICITY_STANDING_CHARGES_COORDINATOR"
DATA_ELECTRICITY_STANDING_CHARGES = "ELECTRICITY_STANDING_CHARGES"
DATA_ELECTRICITY_STANDING_CHARGE_KEY = "ELECTRICITY_STANDING_CHARGES_{}_{}"

DATA_GAS_STANDING_CHARGES_COORDINATOR = "GAS_STANDING_CHARGES_COORDINATOR"
DATA_GAS_STANDING_CHARGES = "GAS_STANDING_CHARGES"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,6 @@ async def async_refresh_electricity_rates_data(

elif (existing_rates_result is not None):
_LOGGER.debug(f"Failed to retrieve new electricity rates for {target_mpan}/{target_serial_number}, so using cached rates")
return existing_rates_result

return existing_rates_result

Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import logging
from datetime import datetime, timedelta
from custom_components.octopus_energy.utils import get_active_tariff_code

from homeassistant.util.dt import (now, as_utc)
from homeassistant.helpers.update_coordinator import (
DataUpdateCoordinator
)

from ..const import (
COORDINATOR_REFRESH_IN_SECONDS,
DATA_ELECTRICITY_STANDING_CHARGE_KEY,
DOMAIN,
DATA_CLIENT,
DATA_ELECTRICITY_STANDING_CHARGES,
DATA_ACCOUNT,
)

Expand All @@ -19,73 +21,89 @@

_LOGGER = logging.getLogger(__name__)

class ElectricityStandingChargeCoordinatorResult:
last_retrieved: datetime
standing_charge: {}

def __init__(self, last_retrieved: datetime, standing_charge: {}):
self.last_retrieved = last_retrieved
self.standing_charge = standing_charge

def get_tariff_code(current: datetime, account_info, target_mpan: str, target_serial_number: str):
if 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
# have to enumerate the different meters being used for each tariff as well.
for meter in point["meters"]:
if active_tariff_code is not None and point["mpan"] == target_mpan and meter["serial_number"] == target_serial_number:
return active_tariff_code

async def async_refresh_electricity_standing_charges_data(
current: datetime,
client: OctopusEnergyApiClient,
account_info,
existing_standing_charges: list
target_mpan: str,
target_serial_number: str,
existing_standing_charges_result: ElectricityStandingChargeCoordinatorResult
):
period_from = as_utc(current.replace(hour=0, minute=0, second=0, microsecond=0))
period_to = period_from + timedelta(days=1)

if (account_info is not None):
tariff_codes = get_current_electricity_agreement_tariff_codes(current, account_info)

period_from = as_utc(current.replace(hour=0, minute=0, second=0, microsecond=0))
period_to = period_from + timedelta(days=1)

standing_charges = {}
for ((meter_point, is_smart_meter), tariff_code) in tariff_codes.items():
key = meter_point

new_standing_charges = None
if ((current.minute % 30) == 0 or
existing_standing_charges is None or
key not in existing_standing_charges or
(existing_standing_charges[key]["valid_from"] is not None and existing_standing_charges[key]["valid_from"] < period_from)):
try:
new_standing_charges = await client.async_get_electricity_standing_charge(tariff_code, period_from, period_to)
_LOGGER.debug(f'Electricity standing charges retrieved for {tariff_code}')
except:
_LOGGER.debug(f'Failed to retrieve electricity standing charges for {tariff_code}')
else:
new_standing_charges = existing_standing_charges[key]

if new_standing_charges is not None:
standing_charges[key] = new_standing_charges
elif (existing_standing_charges is not None and key in existing_standing_charges):
_LOGGER.debug(f"Failed to retrieve new electricity standing charges for {tariff_code}, so using cached standing charges")
standing_charges[key] = existing_standing_charges[key]

return standing_charges
tariff_code = get_tariff_code(current, account_info, target_mpan, target_serial_number)
if tariff_code is None:
return None

new_standing_charge = None
if ((current.minute % 30) == 0 or
existing_standing_charges_result is None or
(existing_standing_charges_result.standing_charge["valid_from"] is not None and existing_standing_charges_result.standing_charge["valid_from"] < period_from)):
try:
new_standing_charge = await client.async_get_electricity_standing_charge(tariff_code, period_from, period_to)
_LOGGER.debug(f'Electricity standing charges retrieved for {target_mpan}/{target_serial_number} ({tariff_code})')
except:
_LOGGER.debug(f'Failed to retrieve electricity standing charges for {target_mpan}/{target_serial_number} ({tariff_code})')

if new_standing_charge is not None:
return ElectricityStandingChargeCoordinatorResult(current, new_standing_charge)
elif (existing_standing_charges_result is not None):
_LOGGER.debug(f"Failed to retrieve new electricity standing charges for {target_mpan}/{target_serial_number} ({tariff_code}), so using cached standing charges")

return existing_standing_charges
return existing_standing_charges_result

async def async_setup_electricity_standing_charges_coordinator(hass, account_id: str):
async def async_setup_electricity_standing_charges_coordinator(hass, target_mpan: str, target_serial_number: str):
key = DATA_ELECTRICITY_STANDING_CHARGE_KEY.format(target_mpan, target_serial_number)

# Reset data rates as we might have new information
hass.data[DOMAIN][DATA_ELECTRICITY_STANDING_CHARGES] = []
hass.data[DOMAIN][key] = None

async def async_update_electricity_standing_charges_data():
"""Fetch data from API endpoint."""
current = now()
client: OctopusEnergyApiClient = hass.data[DOMAIN][DATA_CLIENT]
account_info = hass.data[DOMAIN][DATA_ACCOUNT] if DATA_ACCOUNT in hass.data[DOMAIN] else None
standing_charges = hass.data[DOMAIN][DATA_ELECTRICITY_STANDING_CHARGES] if DATA_ELECTRICITY_STANDING_CHARGES in hass.data[DOMAIN] else {}
standing_charges: ElectricityStandingChargeCoordinatorResult = hass.data[DOMAIN][key] if key in hass.data[DOMAIN] else None

hass.data[DOMAIN][DATA_ELECTRICITY_STANDING_CHARGES] = await async_refresh_electricity_standing_charges_data(
hass.data[DOMAIN][key] = await async_refresh_electricity_standing_charges_data(
current,
client,
account_info,
target_mpan,
target_serial_number,
standing_charges,
)

return hass.data[DOMAIN][DATA_ELECTRICITY_STANDING_CHARGES]
return hass.data[DOMAIN][key]

coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
name="electricity_standing_charges",
name=key,
update_method=async_update_electricity_standing_charges_data,
# Because of how we're using the data, we'll update every minute, but we will only actually retrieve
# data every 30 minutes
update_interval=timedelta(minutes=1),
update_interval=timedelta(minutes=COORDINATOR_REFRESH_IN_SECONDS),
)

await coordinator.async_config_entry_first_refresh()
Expand Down
4 changes: 2 additions & 2 deletions custom_components/octopus_energy/coordinators/gas_rates.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ def __init__(self, last_retrieved: datetime, rates: list):
self.last_retrieved = last_retrieved
self.rates = rates

def get_tariff_code_and_is_smart_meter(current: datetime, account_info, target_mprn: str, target_serial_number: str):
def get_tariff_code(current: datetime, account_info, target_mprn: str, target_serial_number: str):
if len(account_info["gas_meter_points"]) > 0:
for point in account_info["gas_meter_points"]:
active_tariff_code = get_active_tariff_code(current, point["agreements"])
Expand All @@ -60,7 +60,7 @@ async def async_refresh_gas_rates_data(
period_from = as_utc((current - timedelta(days=1)).replace(hour=0, minute=0, second=0, microsecond=0))
period_to = as_utc((current + timedelta(days=2)).replace(hour=0, minute=0, second=0, microsecond=0))

tariff_code = get_tariff_code_and_is_smart_meter(current, account_info, target_mprn, target_serial_number)
tariff_code = get_tariff_code(current, account_info, target_mprn, target_serial_number)
if tariff_code is None:
return None

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ def state(self):
"""Retrieve the current days accumulative consumption"""
consumption_data = self.coordinator.data if self.coordinator is not None and self.coordinator.data is not None else None
rate_data = self._rates_coordinator.data.rates if self._rates_coordinator is not None and self._rates_coordinator.data is not None else None
standing_charge = self._standing_charge_coordinator.data[self._mpan]["value_inc_vat"] if self._standing_charge_coordinator is not None and self._standing_charge_coordinator.data is not None and self._mpan in self._standing_charge_coordinator.data and "value_inc_vat" in self._standing_charge_coordinator.data[self._mpan] else None

standing_charge = self._standing_charge_coordinator.data.standing_charge["value_inc_vat"] if self._standing_charge_coordinator is not None and self._standing_charge_coordinator.data is not None else None
consumption_and_cost = calculate_electricity_consumption_and_cost(
consumption_data,
rate_data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ def state(self):
"""Retrieve the current days accumulative consumption"""
consumption_data = self.coordinator.data if self.coordinator is not None and self.coordinator.data is not None else None
rate_data = self._rates_coordinator.data.rates if self._rates_coordinator is not None and self._rates_coordinator.data is not None else None
standing_charge = self._standing_charge_coordinator.data[self._mpan]["value_inc_vat"] if self._standing_charge_coordinator is not None and self._standing_charge_coordinator.data is not None and self._mpan in self._standing_charge_coordinator.data and "value_inc_vat" in self._standing_charge_coordinator.data[self._mpan] else None
standing_charge = self._standing_charge_coordinator.data.standing_charge["value_inc_vat"] if self._standing_charge_coordinator is not None and self._standing_charge_coordinator.data is not None else None

consumption_and_cost = calculate_electricity_consumption_and_cost(
consumption_data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,8 @@ def state(self):
"""Retrieve the current days accumulative consumption"""
consumption_data = self.coordinator.data if self.coordinator is not None and self.coordinator.data is not None else None
rate_data = self._rates_coordinator.data.rates if self._rates_coordinator is not None and self._rates_coordinator.data is not None else None
standing_charge = self._standing_charge_coordinator.data[self._mpan]["value_inc_vat"] if self._standing_charge_coordinator is not None and self._standing_charge_coordinator.data is not None and self._mpan in self._standing_charge_coordinator.data and "value_inc_vat" in self._standing_charge_coordinator.data[self._mpan] else None

standing_charge = self._standing_charge_coordinator.data.standing_charge["value_inc_vat"] if self._standing_charge_coordinator is not None and self._standing_charge_coordinator.data is not None else None
consumption_and_cost = calculate_electricity_consumption_and_cost(
consumption_data,
rate_data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ def state(self):
"""Retrieve the currently calculated state"""
consumption_data = self.coordinator.data if self.coordinator is not None and self.coordinator.data is not None else None
rate_data = self._rates_coordinator.data.rates if self._rates_coordinator is not None and self._rates_coordinator.data is not None else None
standing_charge = self._standing_charge_coordinator.data[self._mpan]["value_inc_vat"] if self._standing_charge_coordinator is not None and self._standing_charge_coordinator.data is not None and self._mpan in self._standing_charge_coordinator.data and "value_inc_vat" in self._standing_charge_coordinator.data[self._mpan] else None

standing_charge = self._standing_charge_coordinator.data.standing_charge["value_inc_vat"] if self._standing_charge_coordinator is not None and self._standing_charge_coordinator.data is not None else None
consumption_and_cost = calculate_electricity_consumption_and_cost(
consumption_data,
rate_data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ def state(self):
"""Retrieve the currently calculated state"""
consumption_data = self.coordinator.data if self.coordinator is not None and self.coordinator.data is not None else None
rate_data = self._rates_coordinator.data.rates if self._rates_coordinator is not None and self._rates_coordinator.data is not None else None
standing_charge = self._standing_charge_coordinator.data[self._mpan]["value_inc_vat"] if self._standing_charge_coordinator is not None and self._standing_charge_coordinator.data is not None and self._mpan in self._standing_charge_coordinator.data and "value_inc_vat" in self._standing_charge_coordinator.data[self._mpan] else None

standing_charge = self._standing_charge_coordinator.data.standing_charge["value_inc_vat"] if self._standing_charge_coordinator is not None and self._standing_charge_coordinator.data is not None else None
consumption_and_cost = calculate_electricity_consumption_and_cost(
consumption_data,
rate_data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ def state(self):
"""Retrieve the currently calculated state"""
consumption_data = self.coordinator.data if self.coordinator is not None and self.coordinator.data is not None else None
rate_data = self._rates_coordinator.data.rates if self._rates_coordinator is not None and self._rates_coordinator.data is not None else None
standing_charge = self._standing_charge_coordinator.data[self._mpan]["value_inc_vat"] if self._standing_charge_coordinator is not None and self._standing_charge_coordinator.data is not None and self._mpan in self._standing_charge_coordinator.data and "value_inc_vat" in self._standing_charge_coordinator.data[self._mpan] else None

standing_charge = self._standing_charge_coordinator.data.standing_charge["value_inc_vat"] if self._standing_charge_coordinator is not None and self._standing_charge_coordinator.data is not None else None
consumption_and_cost = calculate_electricity_consumption_and_cost(
consumption_data,
rate_data,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def state(self):
"""Retrieve the latest electricity standing charge"""
_LOGGER.debug('Updating OctopusEnergyElectricityCurrentStandingCharge')

standard_charge_result = self.coordinator.data[self._mpan] if self.coordinator is not None and self.coordinator.data is not None and self._mpan in self.coordinator.data else None
standard_charge_result = self.coordinator.data.standing_charge if self.coordinator is not None and self.coordinator.data is not None else None

if standard_charge_result is not None:
self._latest_date = standard_charge_result["valid_from"]
Expand Down
3 changes: 1 addition & 2 deletions custom_components/octopus_energy/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,6 @@ async def async_setup_default_sensors(hass: HomeAssistant, entry, async_add_enti
now = utcnow()

if len(account_info["electricity_meter_points"]) > 0:
electricity_standing_charges_coordinator = await async_setup_electricity_standing_charges_coordinator(hass, config[CONFIG_MAIN_ACCOUNT_ID])
electricity_price_cap = None
if CONFIG_MAIN_ELECTRICITY_PRICE_CAP in config:
electricity_price_cap = config[CONFIG_MAIN_ELECTRICITY_PRICE_CAP]
Expand All @@ -124,11 +123,11 @@ async def async_setup_default_sensors(hass: HomeAssistant, entry, async_add_enti
for meter in point["meters"]:
mpan = point["mpan"]
serial_number = meter["serial_number"]


_LOGGER.info(f'Adding electricity meter; mpan: {mpan}; serial number: {serial_number}')

electricity_rate_coordinator = hass.data[DOMAIN][DATA_ELECTRICITY_RATES_COORDINATOR_KEY.format(mpan, serial_number)]
electricity_standing_charges_coordinator = await async_setup_electricity_standing_charges_coordinator(hass, mpan, serial_number)

entities.append(OctopusEnergyElectricityCurrentRate(hass, electricity_rate_coordinator, meter, point, electricity_tariff_code, electricity_price_cap))
entities.append(OctopusEnergyElectricityPreviousRate(hass, electricity_rate_coordinator, meter, point))
Expand Down
Loading

0 comments on commit 8524005

Please sign in to comment.