Skip to content

Commit

Permalink
fix: reduced API calls for intelligent data during setup
Browse files Browse the repository at this point in the history
  • Loading branch information
BottlecapDave committed Dec 23, 2023
1 parent f896126 commit 381c723
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 132 deletions.
44 changes: 40 additions & 4 deletions custom_components/octopus_energy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .coordinators.electricity_rates import async_setup_electricity_rates_coordinator
from .coordinators.saving_sessions import async_setup_saving_sessions_coordinators
from .statistics import get_statistic_ids_to_remove
from .intelligent import async_mock_intelligent_data, is_intelligent_tariff, mock_intelligent_device

from .config.main import async_migrate_main_config
from .config.target_rates import async_migrate_target_config
Expand All @@ -22,6 +23,9 @@
CONFIG_KIND_ACCOUNT,
CONFIG_MAIN_OLD_API_KEY,
CONFIG_VERSION,
DATA_INTELLIGENT_DEVICE,
DATA_INTELLIGENT_MPAN,
DATA_INTELLIGENT_SERIAL_NUMBER,
DATA_OCTOPLUS_SUPPORTED,
DOMAIN,

Expand Down Expand Up @@ -81,7 +85,6 @@ async def async_setup_entry(hass, entry):

if CONFIG_MAIN_API_KEY in config:
await async_setup_dependencies(hass, config)

await hass.config_entries.async_forward_entry_setups(entry, ACCOUNT_PLATFORMS)
elif CONFIG_TARGET_NAME in config:
if DOMAIN not in hass.data or DATA_ACCOUNT not in hass.data[DOMAIN]:
Expand Down Expand Up @@ -139,10 +142,13 @@ async def async_setup_dependencies(hass, config):
mprn = point["mprn"]
for meter in point["meters"]:
serial_number = meter["serial_number"]
device = device_registry.async_get_device(identifiers={(DOMAIN, f"electricity_{serial_number}_{mprn}")})
if device is not None:
device_registry.async_remove_device(device.id)
intelligent_device = device_registry.async_get_device(identifiers={(DOMAIN, f"electricity_{serial_number}_{mprn}")})
if intelligent_device is not None:
device_registry.async_remove_device(intelligent_device.id)

has_intelligent_tariff = False
intelligent_mpan = None
intelligent_serial_number = None
now = utcnow()
for point in account_info["electricity_meter_points"]:
# We only care about points that have active agreements
Expand All @@ -155,6 +161,36 @@ async def async_setup_dependencies(hass, config):
is_smart_meter = meter["is_smart_meter"]
await async_setup_electricity_rates_coordinator(hass, mpan, serial_number, is_smart_meter, is_export_meter)

if meter["is_export"] == False:
if is_intelligent_tariff(tariff_code):
intelligent_mpan = mpan
intelligent_serial_number = serial_number
has_intelligent_tariff = True

should_mock_intelligent_data = await async_mock_intelligent_data(hass)
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:
for meter in point["meters"]:
intelligent_mpan = point["mpan"]
intelligent_serial_number = meter["serial_number"]
break

if has_intelligent_tariff or should_mock_intelligent_data:
client: OctopusEnergyApiClient = hass.data[DOMAIN][DATA_CLIENT]

account_id = hass.data[DOMAIN][DATA_ACCOUNT_ID]
if should_mock_intelligent_data:
intelligent_device = mock_intelligent_device()
else:
intelligent_device = await client.async_get_intelligent_device(account_id)

hass.data[DOMAIN][DATA_INTELLIGENT_DEVICE] = intelligent_device
hass.data[DOMAIN][DATA_INTELLIGENT_MPAN] = intelligent_mpan
hass.data[DOMAIN][DATA_INTELLIGENT_SERIAL_NUMBER] = intelligent_serial_number

await async_setup_account_info_coordinator(hass, config[CONFIG_MAIN_ACCOUNT_ID])

await async_setup_intelligent_dispatches_coordinator(hass)
Expand Down
2 changes: 2 additions & 0 deletions custom_components/octopus_energy/api_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@
intelligent_device_query = '''query {{
registeredKrakenflexDevice(accountNumber: "{account_id}") {{
krakenflexDeviceId
provider
vehicleMake
vehicleModel
vehicleBatterySizeInKwh
Expand Down Expand Up @@ -1120,6 +1121,7 @@ async def async_get_intelligent_device(self, account_id: str):
device = response_body["data"]["registeredKrakenflexDevice"]
return {
"krakenflexDeviceId": device["krakenflexDeviceId"],
"provider": device["provider"],
"vehicleMake": device["vehicleMake"],
"vehicleModel": device["vehicleModel"],
"vehicleBatterySizeInKwh": float(device["vehicleBatterySizeInKwh"]) if "vehicleBatterySizeInKwh" in device and device["vehicleBatterySizeInKwh"] is not None else None,
Expand Down
44 changes: 10 additions & 34 deletions custom_components/octopus_energy/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@
from .octoplus.saving_sessions import OctopusEnergySavingSessions
from .target_rates.target_rate import OctopusEnergyTargetRate
from .intelligent.dispatching import OctopusEnergyIntelligentDispatching
from .api_client import OctopusEnergyApiClient
from .intelligent import async_mock_intelligent_data, is_intelligent_tariff, mock_intelligent_device
from .utils import get_active_tariff_code
from .intelligent import get_intelligent_features

from .const import (
DATA_ACCOUNT_ID,
DATA_CLIENT,
DATA_INTELLIGENT_DEVICE,
DATA_INTELLIGENT_DISPATCHES_COORDINATOR,
DATA_INTELLIGENT_MPAN,
DATA_INTELLIGENT_SERIAL_NUMBER,
DOMAIN,

CONFIG_MAIN_API_KEY,
Expand Down Expand Up @@ -74,12 +75,8 @@ async def async_setup_main_sensors(hass, entry, async_add_entities):
account_result = hass.data[DOMAIN][DATA_ACCOUNT]
account_info = account_result.account if account_result is not None else None
account_id = hass.data[DOMAIN][DATA_ACCOUNT_ID]
client = hass.data[DOMAIN][DATA_CLIENT]

now = utcnow()
has_intelligent_tariff = False
intelligent_mpan = None
intelligent_serial_number = None
entities = [OctopusEnergySavingSessions(hass, saving_session_coordinator, account_id)]
if len(account_info["electricity_meter_points"]) > 0:

Expand All @@ -93,36 +90,15 @@ async def async_setup_main_sensors(hass, entry, async_add_entities):
electricity_rate_coordinator = hass.data[DOMAIN][DATA_ELECTRICITY_RATES_COORDINATOR_KEY.format(mpan, serial_number)]

entities.append(OctopusEnergyElectricityOffPeak(hass, electricity_rate_coordinator, meter, point))
if meter["is_export"] == False:

if is_intelligent_tariff(tariff_code):
intelligent_mpan = mpan
intelligent_serial_number = serial_number
has_intelligent_tariff = True

should_mock_intelligent_data = await async_mock_intelligent_data(hass)
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:
for meter in point["meters"]:
intelligent_mpan = point["mpan"]
intelligent_serial_number = meter["serial_number"]
break

if has_intelligent_tariff or should_mock_intelligent_data:
intelligent_device = hass.data[DOMAIN][DATA_INTELLIGENT_DEVICE]
intelligent_mpan = hass.data[DOMAIN][DATA_INTELLIGENT_MPAN]
intelligent_serial_number = hass.data[DOMAIN][DATA_INTELLIGENT_SERIAL_NUMBER]
if intelligent_device is not None and intelligent_mpan is not None and intelligent_serial_number is not None:
intelligent_features = get_intelligent_features(intelligent_device["provider"])
coordinator = hass.data[DOMAIN][DATA_INTELLIGENT_DISPATCHES_COORDINATOR]
client: OctopusEnergyApiClient = hass.data[DOMAIN][DATA_CLIENT]

account_id = hass.data[DOMAIN][DATA_ACCOUNT_ID]
if should_mock_intelligent_data:
device = mock_intelligent_device()
else:
device = await client.async_get_intelligent_device(account_id)

electricity_rate_coordinator = hass.data[DOMAIN][DATA_ELECTRICITY_RATES_COORDINATOR_KEY.format(intelligent_mpan, intelligent_serial_number)]
entities.append(OctopusEnergyIntelligentDispatching(hass, coordinator, electricity_rate_coordinator, intelligent_mpan, device, account_id))
entities.append(OctopusEnergyIntelligentDispatching(hass, coordinator, electricity_rate_coordinator, intelligent_mpan, intelligent_device, account_id, intelligent_features.planned_dispatches_supported))

if len(entities) > 0:
async_add_entities(entities, True)
Expand Down
3 changes: 3 additions & 0 deletions custom_components/octopus_energy/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@
DATA_KNOWN_TARIFF = "KNOWN_TARIFF"
DATA_GAS_RATES_COORDINATOR_KEY = "DATA_GAS_RATES_COORDINATOR_{}_{}"
DATA_GAS_RATES_KEY = "GAS_RATES_{}_{}"
DATA_INTELLIGENT_DEVICE = "INTELLIGENT_DEVICE"
DATA_INTELLIGENT_MPAN = "INTELLIGENT_MPAN"
DATA_INTELLIGENT_SERIAL_NUMBER = "INTELLIGENT_SERIAL_NUMBER"
DATA_INTELLIGENT_DISPATCHES = "INTELLIGENT_DISPATCHES"
DATA_INTELLIGENT_DISPATCHES_COORDINATOR = "INTELLIGENT_DISPATCHES_COORDINATOR"
DATA_INTELLIGENT_SETTINGS = "INTELLIGENT_SETTINGS"
Expand Down
49 changes: 48 additions & 1 deletion custom_components/octopus_energy/intelligent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def mock_intelligent_settings():
def mock_intelligent_device():
return {
"krakenflexDeviceId": "1",
"provider": FULLY_SUPPORTED_INTELLIGENT_PROVIDERS[0],
"vehicleMake": "Tesla",
"vehicleModel": "Model Y",
"vehicleBatterySizeInKwh": 75.0,
Expand Down Expand Up @@ -177,4 +178,50 @@ def dispatches_to_dictionary_list(dispatches: list[IntelligentDispatchItem]):
"location": dispatch.location
})

return items
return items

class IntelligentFeatures:
bump_charge_supported: bool
charge_limit_supported: bool
planned_dispatches_supported: bool
ready_time_supported: bool
smart_charge_supported: bool

def __init__(self,
bump_charge_supported: bool,
charge_limit_supported: bool,
planned_dispatches_supported: bool,
ready_time_supported: bool,
smart_charge_supported: bool):
self.bump_charge_supported = bump_charge_supported
self.charge_limit_supported = charge_limit_supported
self.planned_dispatches_supported = planned_dispatches_supported
self.ready_time_supported = ready_time_supported
self.smart_charge_supported = smart_charge_supported

FULLY_SUPPORTED_INTELLIGENT_PROVIDERS = [
"DAIKIN",
"ECOBEE",
"ENERGIZER",
"ENPHASE",
"ENODE",
"GIVENERGY",
"HUAWEI",
"JEDLIX",
"MYENERGI",
"OCPP_WALLBOX",
"SENSI",
"SMARTCAR",
"TESLA",
"SMART_PEAR",
]

def get_intelligent_features(provider: str) -> IntelligentFeatures:
if provider in FULLY_SUPPORTED_INTELLIGENT_PROVIDERS:
return IntelligentFeatures(True, True, True, True, True)
elif provider == "OHME":
# return IntelligentFeatures(False, False, False, False, False)
return IntelligentFeatures(True, True, True, True, True)

_LOGGER.warn(f"Unexpected intelligent provider '{provider}'")
return IntelligentFeatures(False, False, False, False, False)
8 changes: 5 additions & 3 deletions custom_components/octopus_energy/intelligent/dispatching.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
class OctopusEnergyIntelligentDispatching(CoordinatorEntity, BinarySensorEntity, OctopusEnergyIntelligentSensor, RestoreEntity):
"""Sensor for determining if an intelligent is dispatching."""

def __init__(self, hass: HomeAssistant, coordinator, rates_coordinator, mpan: str, device, account_id: str):
def __init__(self, hass: HomeAssistant, coordinator, rates_coordinator, mpan: str, device, account_id: str, planned_dispatches_supported: bool):
"""Init sensor."""

CoordinatorEntity.__init__(self, coordinator)
Expand All @@ -38,6 +38,7 @@ def __init__(self, hass: HomeAssistant, coordinator, rates_coordinator, mpan: st
self._mpan = mpan
self._account_id = account_id
self._state = None
self._planned_dispatches_supported = planned_dispatches_supported
self._attributes = {
"planned_dispatches": [],
"completed_dispatches": [],
Expand Down Expand Up @@ -75,10 +76,11 @@ def is_on(self):
rates = self._rates_coordinator.data.rates if self._rates_coordinator is not None and self._rates_coordinator.data is not None else None

current_date = utcnow()
self._state = is_in_planned_dispatch(current_date, result.dispatches.planned if result.dispatches is not None else []) or is_off_peak(current_date, rates)
planned_dispatches = result.dispatches.planned if result.dispatches is not None and self._planned_dispatches_supported else []
self._state = is_in_planned_dispatch(current_date, planned_dispatches) or is_off_peak(current_date, rates)

self._attributes = {
"planned_dispatches": dispatches_to_dictionary_list(result.dispatches.planned if result.dispatches is not None else []) if result is not None else [],
"planned_dispatches": dispatches_to_dictionary_list(planned_dispatches) if result is not None else [],
"completed_dispatches": dispatches_to_dictionary_list(result.dispatches.completed if result.dispatches is not None else []) if result is not None else [],
"data_last_retrieved": result.last_retrieved if result is not None else None,
"last_evaluated": current_date
Expand Down
47 changes: 15 additions & 32 deletions custom_components/octopus_energy/number.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
from datetime import timedelta
import logging

from homeassistant.util.dt import (utcnow)

from .intelligent import get_intelligent_features
from .intelligent.charge_limit import OctopusEnergyIntelligentChargeLimit
from .api_client import OctopusEnergyApiClient
from .intelligent import async_mock_intelligent_data, is_intelligent_tariff, mock_intelligent_device
from .utils import get_active_tariff_code

from .const import (
DATA_ACCOUNT_ID,
DATA_CLIENT,
DATA_INTELLIGENT_DEVICE,
DOMAIN,

CONFIG_MAIN_API_KEY,
Expand All @@ -32,31 +27,19 @@ async def async_setup_entry(hass, entry, async_add_entities):
async def async_setup_intelligent_sensors(hass, async_add_entities):
_LOGGER.debug('Setting up intelligent sensors')

entities = []

account_result = hass.data[DOMAIN][DATA_ACCOUNT]
account_info = account_result.account if account_result is not None else None

now = utcnow()
has_intelligent_tariff = False
if len(account_info["electricity_meter_points"]) > 0:

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"])
if is_intelligent_tariff(tariff_code):
has_intelligent_tariff = True
break

should_mock_intelligent_data = await async_mock_intelligent_data(hass)
if has_intelligent_tariff or should_mock_intelligent_data:
coordinator = hass.data[DOMAIN][DATA_INTELLIGENT_SETTINGS_COORDINATOR]
client: OctopusEnergyApiClient = hass.data[DOMAIN][DATA_CLIENT]

account_id = hass.data[DOMAIN][DATA_ACCOUNT_ID]
if should_mock_intelligent_data:
device = mock_intelligent_device()
else:
device = await client.async_get_intelligent_device(account_id)

async_add_entities([
OctopusEnergyIntelligentChargeLimit(hass, coordinator, client, device, account_id),
], True)
account_id = account_info["id"]
client = hass.data[DOMAIN][DATA_CLIENT]
intelligent_device = hass.data[DOMAIN][DATA_INTELLIGENT_DEVICE]
if intelligent_device is not None:
intelligent_features = get_intelligent_features(intelligent_device["provider"])
settings_coordinator = hass.data[DOMAIN][DATA_INTELLIGENT_SETTINGS_COORDINATOR]

if intelligent_features.charge_limit_supported == True:
entities.append(OctopusEnergyIntelligentChargeLimit(hass, settings_coordinator, client, intelligent_device, account_id))

async_add_entities(entities, True)

0 comments on commit 381c723

Please sign in to comment.