Skip to content

Commit

Permalink
fix: Accounted when intelligent device can come back with null data f…
Browse files Browse the repository at this point in the history
…rom OE
  • Loading branch information
BottlecapDave committed Apr 28, 2024
1 parent e8786df commit 53db610
Show file tree
Hide file tree
Showing 12 changed files with 146 additions and 62 deletions.
9 changes: 5 additions & 4 deletions custom_components/octopus_energy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,10 @@ async def async_setup_dependencies(hass, config):
else:
intelligent_device = await client.async_get_intelligent_device(account_id)

hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DEVICE] = intelligent_device
hass.data[DOMAIN][account_id][DATA_INTELLIGENT_MPAN] = intelligent_mpan
hass.data[DOMAIN][account_id][DATA_INTELLIGENT_SERIAL_NUMBER] = intelligent_serial_number
if intelligent_device is not None:
hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DEVICE] = intelligent_device
hass.data[DOMAIN][account_id][DATA_INTELLIGENT_MPAN] = intelligent_mpan
hass.data[DOMAIN][account_id][DATA_INTELLIGENT_SERIAL_NUMBER] = intelligent_serial_number

for point in account_info["electricity_meter_points"]:
# We only care about points that have active agreements
Expand All @@ -268,7 +269,7 @@ async def async_setup_dependencies(hass, config):
is_export_meter = meter["is_export"]
is_smart_meter = meter["is_smart_meter"]
tariff_override = await async_get_tariff_override(hass, mpan, serial_number)
planned_dispatches_supported = get_intelligent_features(intelligent_device["provider"]).planned_dispatches_supported if intelligent_device is not None else True
planned_dispatches_supported = get_intelligent_features(intelligent_device.provider).planned_dispatches_supported if intelligent_device is not None else True
await async_setup_electricity_rates_coordinator(hass, account_id, mpan, serial_number, is_smart_meter, is_export_meter, planned_dispatches_supported, tariff_override)

await async_setup_account_info_coordinator(hass, account_id)
Expand Down
25 changes: 13 additions & 12 deletions custom_components/octopus_energy/api_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
)


from .intelligent_device import IntelligentDevice
from .octoplus import RedeemOctoplusPointsResponse
from .intelligent_settings import IntelligentSettings
from .intelligent_dispatches import IntelligentDispatchItem, IntelligentDispatches
Expand Down Expand Up @@ -1203,7 +1204,7 @@ async def async_turn_off_intelligent_smart_charge(
_LOGGER.warning(f'Failed to connect. Timeout of {self._timeout} exceeded.')
raise TimeoutException()

async def async_get_intelligent_device(self, account_id: str):
async def async_get_intelligent_device(self, account_id: str) -> IntelligentDevice:
"""Get the user's intelligent dispatches"""
await self.async_refresh_token()

Expand All @@ -1219,17 +1220,17 @@ async def async_get_intelligent_device(self, account_id: str):
if (response_body is not None and "data" in response_body and
"registeredKrakenflexDevice" in response_body["data"]):
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,
"chargePointMake": device["chargePointMake"],
"chargePointModel": device["chargePointModel"],
"chargePointPowerInKw": float(device["chargePointPowerInKw"]) if "chargePointPowerInKw" in device and device["chargePointPowerInKw"] is not None else None,

}
if device["krakenflexDeviceId"] is not None:
return IntelligentDevice(
device["krakenflexDeviceId"],
device["provider"],
device["vehicleMake"],
device["vehicleModel"],
float(device["vehicleBatterySizeInKwh"]) if "vehicleBatterySizeInKwh" in device and device["vehicleBatterySizeInKwh"] is not None else None,
device["chargePointMake"],
device["chargePointModel"],
float(device["chargePointPowerInKw"]) if "chargePointPowerInKw" in device and device["chargePointPowerInKw"] is not None else None
)
else:
_LOGGER.error("Failed to retrieve intelligent device")

Expand Down
29 changes: 29 additions & 0 deletions custom_components/octopus_energy/api_client/intelligent_device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class IntelligentDevice:
krakenflexDeviceId: str
provider: str
vehicleMake:str
vehicleModel: str
vehicleBatterySizeInKwh: float | None
chargePointMake: str
chargePointModel: str
chargePointPowerInKw: float | None

def __init__(
self,
krakenflexDeviceId: str,
provider: str,
vehicleMake:str,
vehicleModel: str,
vehicleBatterySizeInKwh: float | None,
chargePointMake: str,
chargePointModel: str,
chargePointPowerInKw: float | None
):
self.krakenflexDeviceId = krakenflexDeviceId
self.provider = provider
self.vehicleMake = vehicleMake
self.vehicleModel = vehicleModel
self.vehicleBatterySizeInKwh = vehicleBatterySizeInKwh
self.chargePointMake = chargePointMake
self.chargePointModel = chargePointModel
self.chargePointPowerInKw = chargePointPowerInKw
5 changes: 3 additions & 2 deletions custom_components/octopus_energy/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from .greenness_forecast.highlighted import OctopusEnergyGreennessForecastHighlighted
from .utils import get_active_tariff_code
from .intelligent import get_intelligent_features
from .api_client.intelligent_device import IntelligentDevice

from .const import (
CONFIG_KIND,
Expand Down Expand Up @@ -98,11 +99,11 @@ async def async_setup_main_sensors(hass, entry, async_add_entities):

entities.append(OctopusEnergyElectricityOffPeak(hass, electricity_rate_coordinator, meter, point))

intelligent_device = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DEVICE] if DATA_INTELLIGENT_DEVICE in hass.data[DOMAIN][account_id] else None
intelligent_device: IntelligentDevice = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DEVICE] if DATA_INTELLIGENT_DEVICE in hass.data[DOMAIN][account_id] else None
intelligent_mpan = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_MPAN] if DATA_INTELLIGENT_MPAN in hass.data[DOMAIN][account_id] else None
intelligent_serial_number = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_SERIAL_NUMBER] if DATA_INTELLIGENT_SERIAL_NUMBER in hass.data[DOMAIN][account_id] else None
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"])
intelligent_features = get_intelligent_features(intelligent_device.provider)
coordinator = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DISPATCHES_COORDINATOR]
electricity_rate_coordinator = hass.data[DOMAIN][account_id][DATA_ELECTRICITY_RATES_COORDINATOR_KEY.format(intelligent_mpan, intelligent_serial_number)]
entities.append(OctopusEnergyIntelligentDispatching(hass, coordinator, electricity_rate_coordinator, intelligent_mpan, intelligent_device, account_id, intelligent_features.planned_dispatches_supported))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from ..const import (
COORDINATOR_REFRESH_IN_SECONDS,
DATA_INTELLIGENT_DEVICE,
DOMAIN,

DATA_CLIENT,
Expand All @@ -25,6 +26,7 @@
from ..api_client import ApiException, OctopusEnergyApiClient
from ..api_client.intelligent_dispatches import IntelligentDispatches
from . import BaseCoordinatorResult
from ..api_client.intelligent_device import IntelligentDevice

from ..intelligent import async_mock_intelligent_data, clean_previous_dispatches, dictionary_list_to_dispatches, dispatches_to_dictionary_list, has_intelligent_tariff, mock_intelligent_dispatches

Expand Down Expand Up @@ -58,6 +60,7 @@ async def async_refresh_intelligent_dispatches(
current: datetime,
client: OctopusEnergyApiClient,
account_info,
intelligent_device: IntelligentDevice,
existing_intelligent_dispatches_result: IntelligentDispatchesCoordinatorResult,
is_data_mocked: bool,
async_merge_dispatch_data: Callable[[str, list], Awaitable[list]]
Expand All @@ -66,7 +69,7 @@ async def async_refresh_intelligent_dispatches(
account_id = account_info["id"]
if (existing_intelligent_dispatches_result is None or current >= existing_intelligent_dispatches_result.next_refresh):
dispatches = None
if has_intelligent_tariff(current, account_info):
if has_intelligent_tariff(current, account_info) and intelligent_device is not None:
try:
dispatches = await client.async_get_intelligent_dispatches(account_id)
_LOGGER.debug(f'Intelligent dispatches retrieved for account {account_id}')
Expand Down Expand Up @@ -120,6 +123,7 @@ async def async_update_intelligent_dispatches_data():
current,
client,
account_info,
hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DEVICE] if DATA_INTELLIGENT_DEVICE in hass.data[DOMAIN][account_id] else None,
hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DISPATCHES] if DATA_INTELLIGENT_DISPATCHES in hass.data[DOMAIN][account_id] else None,
await async_mock_intelligent_data(hass, account_id),
lambda account_id, completed_dispatches: async_merge_dispatch_data(hass, account_id, completed_dispatches)
Expand Down
21 changes: 11 additions & 10 deletions custom_components/octopus_energy/intelligent/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

from ..api_client.intelligent_settings import IntelligentSettings
from ..api_client.intelligent_dispatches import IntelligentDispatchItem, IntelligentDispatches
from ..api_client.intelligent_device import IntelligentDevice

mock_intelligent_data_key = "MOCK_INTELLIGENT_DATA"

Expand Down Expand Up @@ -97,16 +98,16 @@ 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,
"chargePointMake": "MyEnergi",
"chargePointModel": "Zappi",
"chargePointPowerInKw": 6.5
}
return IntelligentDevice(
"1",
FULLY_SUPPORTED_INTELLIGENT_PROVIDERS[0],
"Tesla",
"Model Y",
75.0,
"MyEnergi",
"Zappi",
6.5
)

def is_intelligent_tariff(tariff_code: str):
parts = get_tariff_parts(tariff_code.upper())
Expand Down
9 changes: 5 additions & 4 deletions custom_components/octopus_energy/intelligent/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
from ..const import (
DOMAIN,
)
from ..api_client.intelligent_device import IntelligentDevice

class OctopusEnergyIntelligentSensor:
def __init__(self, device):
def __init__(self, device: IntelligentDevice):
"""Init sensor"""

self._device = device
self._attr_device_info = DeviceInfo(
identifiers={
(DOMAIN, self._device["krakenflexDeviceId"] if "krakenflexDeviceId" in self._device and self._device["krakenflexDeviceId"] is not None else "charger-1")
(DOMAIN, self._device.krakenflexDeviceId if self._device.krakenflexDeviceId is not None else "charger-1")
},
name="Charger",
connections=set(),
manufacturer=self._device["chargePointMake"],
model=self._device["chargePointModel"]
manufacturer=self._device.chargePointMake,
model=self._device.chargePointModel
)
47 changes: 24 additions & 23 deletions custom_components/octopus_energy/intelligent/dispatching.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@
from .base import OctopusEnergyIntelligentSensor
from ..coordinators.intelligent_dispatches import IntelligentDispatchesCoordinatorResult
from ..utils.attributes import dict_to_typed_dict
from ..api_client.intelligent_device import IntelligentDevice

_LOGGER = logging.getLogger(__name__)

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, planned_dispatches_supported: bool):
def __init__(self, hass: HomeAssistant, coordinator, rates_coordinator, mpan: str, device: IntelligentDevice, account_id: str, planned_dispatches_supported: bool):
"""Init sensor."""

CoordinatorEntity.__init__(self, coordinator)
Expand All @@ -41,18 +42,7 @@ def __init__(self, hass: HomeAssistant, coordinator, rates_coordinator, mpan: st
self._account_id = account_id
self._state = None
self._planned_dispatches_supported = planned_dispatches_supported
self._attributes = {
"planned_dispatches": [],
"completed_dispatches": [],
"last_evaluated": None,
"provider": device["provider"],
"vehicle_battery_size_in_kwh": device["vehicleBatterySizeInKwh"],
"charge_point_power_in_kw": device["chargePointPowerInKw"],
"current_start": None,
"current_end": None,
"next_start": None,
"next_end": None,
}
self.__init_attributes__([], [], None, None)

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

Expand Down Expand Up @@ -80,6 +70,21 @@ def extra_state_attributes(self):
def is_on(self):
return self._state

def __init_attributes__(self, planned_dispatches, completed_dispatches, data_last_retrieved, last_evaluated):
self._attributes = {
"planned_dispatches": planned_dispatches,
"completed_dispatches": completed_dispatches,
"data_last_retrieved": data_last_retrieved,
"last_evaluated": last_evaluated,
"provider": self._device.provider,
"vehicle_battery_size_in_kwh": self._device.vehicleBatterySizeInKwh,
"charge_point_power_in_kw": self._device.chargePointPowerInKw,
"current_start": None,
"current_end": None,
"next_start": None,
"next_end": None,
}

@callback
def _handle_coordinator_update(self) -> None:
"""Determine if OE is currently dispatching energy."""
Expand All @@ -89,16 +94,12 @@ def _handle_coordinator_update(self) -> None:
current_date = utcnow()
planned_dispatches = result.dispatches.planned if result is not None and result.dispatches is not None and self._planned_dispatches_supported else []

self._attributes = {
"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 is not None and 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,
"current_start": None,
"current_end": None,
"next_start": None,
"next_end": None,
}
self.__init_attributes__(
dispatches_to_dictionary_list(planned_dispatches) if result is not None else [],
dispatches_to_dictionary_list(result.dispatches.completed if result is not None and result.dispatches is not None else []) if result is not None else [],
result.last_retrieved if result is not None else None,
current_date
)

off_peak_times = get_off_peak_times(current_date, rates, True)
is_dispatching = False
Expand Down
5 changes: 3 additions & 2 deletions custom_components/octopus_energy/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from .intelligent import get_intelligent_features
from .intelligent.charge_limit import OctopusEnergyIntelligentChargeLimit
from .api_client.intelligent_device import IntelligentDevice

from .const import (
CONFIG_ACCOUNT_ID,
Expand Down Expand Up @@ -37,9 +38,9 @@ async def async_setup_intelligent_sensors(hass, config, async_add_entities):
account_id = config[CONFIG_ACCOUNT_ID]

client = hass.data[DOMAIN][account_id][DATA_CLIENT]
intelligent_device = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DEVICE] if DATA_INTELLIGENT_DEVICE in hass.data[DOMAIN][account_id] else None
intelligent_device: IntelligentDevice = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DEVICE] if DATA_INTELLIGENT_DEVICE in hass.data[DOMAIN][account_id] else None
if intelligent_device is not None:
intelligent_features = get_intelligent_features(intelligent_device["provider"])
intelligent_features = get_intelligent_features(intelligent_device.provider)
settings_coordinator = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_SETTINGS_COORDINATOR]

if intelligent_features.charge_limit_supported == True:
Expand Down
5 changes: 3 additions & 2 deletions custom_components/octopus_energy/switch.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from .intelligent.bump_charge import OctopusEnergyIntelligentBumpCharge
from .api_client import OctopusEnergyApiClient
from .intelligent import get_intelligent_features
from .api_client.intelligent_device import IntelligentDevice

from .const import (
CONFIG_ACCOUNT_ID,
Expand Down Expand Up @@ -44,9 +45,9 @@ async def async_setup_intelligent_sensors(hass, config, async_add_entities):

account_id = account_info["id"]
client = hass.data[DOMAIN][account_id][DATA_CLIENT]
intelligent_device = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DEVICE] if DATA_INTELLIGENT_DEVICE in hass.data[DOMAIN][account_id] else None
intelligent_device: IntelligentDevice = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DEVICE] if DATA_INTELLIGENT_DEVICE in hass.data[DOMAIN][account_id] else None
if intelligent_device is not None:
intelligent_features = get_intelligent_features(intelligent_device["provider"])
intelligent_features = get_intelligent_features(intelligent_device.provider)
settings_coordinator = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_SETTINGS_COORDINATOR]
dispatches_coordinator = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DISPATCHES_COORDINATOR]
client: OctopusEnergyApiClient = hass.data[DOMAIN][account_id][DATA_CLIENT]
Expand Down
5 changes: 3 additions & 2 deletions custom_components/octopus_energy/time.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from .intelligent.ready_time import OctopusEnergyIntelligentReadyTime
from .api_client import OctopusEnergyApiClient
from .intelligent import get_intelligent_features
from .api_client.intelligent_device import IntelligentDevice

from .const import (
CONFIG_ACCOUNT_ID,
Expand Down Expand Up @@ -39,9 +40,9 @@ async def async_setup_intelligent_sensors(hass, config, async_add_entities):
account_id = config[CONFIG_ACCOUNT_ID]

client = hass.data[DOMAIN][account_id][DATA_CLIENT]
intelligent_device = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DEVICE] if DATA_INTELLIGENT_DEVICE in hass.data[DOMAIN][account_id] else None
intelligent_device: IntelligentDevice = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_DEVICE] if DATA_INTELLIGENT_DEVICE in hass.data[DOMAIN][account_id] else None
if intelligent_device is not None:
intelligent_features = get_intelligent_features(intelligent_device["provider"])
intelligent_features = get_intelligent_features(intelligent_device.provider)
settings_coordinator = hass.data[DOMAIN][account_id][DATA_INTELLIGENT_SETTINGS_COORDINATOR]
client: OctopusEnergyApiClient = hass.data[DOMAIN][account_id][DATA_CLIENT]

Expand Down
Loading

0 comments on commit 53db610

Please sign in to comment.