Skip to content

Commit

Permalink
feat: Added new events for new and all saving sessions
Browse files Browse the repository at this point in the history
  • Loading branch information
BottlecapDave committed Nov 9, 2023
1 parent 40c9dd6 commit b2b44b5
Show file tree
Hide file tree
Showing 11 changed files with 525 additions and 103 deletions.
31 changes: 21 additions & 10 deletions custom_components/octopus_energy/api_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@

from .intelligent_settings import IntelligentSettings
from .intelligent_dispatches import IntelligentDispatchItem, IntelligentDispatches
from .saving_sessions import JoinSavingSessionResponse
from .saving_sessions import JoinSavingSessionResponse, SavingSession, SavingSessionsResponse

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -242,12 +242,19 @@

octoplus_saving_session_query = '''query {{
savingSessions {{
events {{
id
rewardPerKwhInOctoPoints
startAt
endAt
}}
account(accountNumber: "{account_id}") {{
hasJoinedCampaign
joinedEvents {{
eventId
startAt
endAt
rewardGivenInOctoPoints
}}
}}
}}
Expand Down Expand Up @@ -462,7 +469,7 @@ async def async_get_account(self, account_id):

return None

async def async_get_saving_sessions(self, account_id: str):
async def async_get_saving_sessions(self, account_id: str) -> SavingSessionsResponse:
"""Get the user's seasons savings"""
await self.async_refresh_token()

Expand All @@ -475,14 +482,18 @@ async def async_get_saving_sessions(self, account_id: str):
response_body = await self.__async_read_response__(account_response, url)

if (response_body is not None and "data" in response_body):
return {
"events": list(map(lambda ev: {
"start": as_utc(parse_datetime(ev["startAt"])),
"end": as_utc(parse_datetime(ev["endAt"]))
}, response_body["data"]["savingSessions"]["account"]["joinedEvents"]))
}
return SavingSessionsResponse(list(map(lambda ev: SavingSession(ev["id"],
as_utc(parse_datetime(ev["startAt"])),
as_utc(parse_datetime(ev["endAt"])),
ev["rewardPerKwhInOctoPoints"]),
response_body["data"]["savingSessions"]["events"])),
list(map(lambda ev: SavingSession(ev["eventId"],
as_utc(parse_datetime(ev["startAt"])),
as_utc(parse_datetime(ev["endAt"])),
ev["rewardGivenInOctoPoints"]),
response_body["data"]["savingSessions"]["account"]["joinedEvents"])))
else:
_LOGGER.error("Failed to retrieve account")
_LOGGER.error("Failed to retrieve saving sessions")

return None

Expand Down Expand Up @@ -1020,7 +1031,7 @@ async def __async_read_response__(self, response, url):
elif response.status not in [401, 403, 404]:
msg = f'Failed to send request ({url}): {response.status}; {text}'
_LOGGER.debug(msg)
raise RequestError(msg)
raise RequestError(msg, [])
return None

data_as_json = None
Expand Down
34 changes: 34 additions & 0 deletions custom_components/octopus_energy/api_client/saving_sessions.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import datetime

class JoinSavingSessionResponse:
is_successful: bool
errors: list[str]
Expand All @@ -9,3 +11,35 @@ def __init__(
):
self.is_successful = is_successful
self.errors = errors

class SavingSession:
id: str
start: datetime
end: datetime
octopoints: int
duration_in_minutes: int

def __init__(
self,
id: str,
start: datetime,
end: datetime,
octopoints: int
):
self.id = id
self.start = start
self.end = end
self.octopoints = octopoints
self.duration_in_minutes = (end - start).total_seconds() / 60

class SavingSessionsResponse:
upcoming_events: list[SavingSession]
joined_events: list[SavingSession]

def __init__(
self,
upcoming_events: list[SavingSession],
joined_events: list[SavingSession]
):
self.upcoming_events = upcoming_events
self.joined_events = joined_events
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 @@ -6,7 +6,7 @@
from homeassistant.util.dt import (utcnow)

from .electricity.off_peak import OctopusEnergyElectricityOffPeak
from .octoplus import OctopusEnergySavingSessions
from .octoplus.saving_sessions import OctopusEnergySavingSessions
from .target_rates.target_rate import OctopusEnergyTargetRate
from .intelligent.dispatching import OctopusEnergyIntelligentDispatching
from .api_client import OctopusEnergyApiClient
Expand Down Expand Up @@ -86,12 +86,13 @@ async def async_setup_main_sensors(hass, entry, async_add_entities):

account_info = hass.data[DOMAIN][DATA_ACCOUNT]
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)]
entities = [OctopusEnergySavingSessions(hass, saving_session_coordinator, client, account_id)]
if len(account_info["electricity_meter_points"]) > 0:

for point in account_info["electricity_meter_points"]:
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 @@ -90,6 +90,9 @@
EVENT_GAS_PREVIOUS_CONSUMPTION_RATES = "octopus_energy_gas_previous_consumption_rates"
EVENT_GAS_PREVIOUS_CONSUMPTION_OVERRIDE_RATES = "octopus_energy_gas_previous_consumption_override_rates"

EVENT_NEW_SAVING_SESSION = "octopus_energy_new_saving_session"
EVENT_ALL_SAVING_SESSIONS = "octopus_energy_all_saving_sessions"

# During BST, two records are returned before the rest of the data is available
MINIMUM_CONSUMPTION_DATA_LENGTH = 3

Expand Down
105 changes: 95 additions & 10 deletions custom_components/octopus_energy/coordinators/saving_sessions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
from datetime import timedelta
from datetime import datetime, timedelta
from typing import Callable, Any

from homeassistant.util.dt import (now)
from homeassistant.helpers.update_coordinator import (
Expand All @@ -13,32 +14,116 @@
DATA_ACCOUNT_ID,
DATA_SAVING_SESSIONS,
DATA_SAVING_SESSIONS_COORDINATOR,
EVENT_ALL_SAVING_SESSIONS,
EVENT_NEW_SAVING_SESSION,
)

from ..api_client import OctopusEnergyApiClient
from ..api_client.saving_sessions import SavingSession

_LOGGER = logging.getLogger(__name__)

class SavingSessionsCoordinatorResult:
last_retrieved: datetime
nonjoined_events: list[SavingSession]
joined_events: list[SavingSession]

def __init__(self, last_retrieved: datetime, nonjoined_events: list[SavingSession], joined_events: list[SavingSession]):
self.last_retrieved = last_retrieved
self.nonjoined_events = nonjoined_events
self.joined_events = joined_events

def filter_nonjoined_events(current: datetime, upcoming_events: list[SavingSession], joined_events: list[SavingSession]) -> list[SavingSession]:
filtered_events = []
for upcoming_event in upcoming_events:

is_joined = False
for joined_event in joined_events:
if joined_event.id == upcoming_event.id:
is_joined = True
break

if (upcoming_event.start >= current and is_joined == False):
filtered_events.append(upcoming_event)

return filtered_events

async def async_refresh_saving_sessions(
current: datetime,
client: OctopusEnergyApiClient,
account_id: str,
existing_saving_sessions_result: SavingSessionsCoordinatorResult,
fire_event: Callable[[str, "dict[str, Any]"], None],
) -> SavingSessionsCoordinatorResult:
if existing_saving_sessions_result is None or current.minute % 30 == 0:
try:
result = await client.async_get_saving_sessions(account_id)
nonjoined_events = filter_nonjoined_events(current, result.upcoming_events, result.joined_events)

for nonjoined_event in nonjoined_events:
is_new = True

if existing_saving_sessions_result is not None:
for existing_nonjoined_event in existing_saving_sessions_result.nonjoined_events:
if existing_nonjoined_event.id == nonjoined_event.id:
is_new = False
break

if is_new:
fire_event(EVENT_NEW_SAVING_SESSION, {
"account_id": account_id,
"event_id": nonjoined_event.id,
"event_start": nonjoined_event.start,
"event_end": nonjoined_event.end,
"event_octopoints": nonjoined_event.octopoints
})

fire_event(EVENT_ALL_SAVING_SESSIONS, {
"account_id": account_id,
"nonjoined_events": list(map(lambda ev: {
"id": ev.id,
"start": ev.start,
"end": ev.end,
"octopoints": ev.octopoints
}, nonjoined_events)),
"joined_events": list(map(lambda ev: {
"id": ev.id,
"start": ev.start,
"end": ev.end,
"octopoints": ev.octopoints
}, result.joined_events)),
})

return SavingSessionsCoordinatorResult(current, nonjoined_events, result.joined_events)
except:
_LOGGER.debug('Failed to retrieve saving session information')

return existing_saving_sessions_result

async def async_setup_saving_sessions_coordinators(hass):
account_id = hass.data[DOMAIN][DATA_ACCOUNT_ID]

async def async_update_saving_sessions():
"""Fetch data from API endpoint."""
# Only get data every half hour or if we don't have any data
current = now()
client: OctopusEnergyApiClient = hass.data[DOMAIN][DATA_CLIENT]
if DATA_SAVING_SESSIONS not in hass.data[DOMAIN] or current.minute % 30 == 0:

try:
savings = await client.async_get_saving_sessions(hass.data[DOMAIN][DATA_ACCOUNT_ID])
hass.data[DOMAIN][DATA_SAVING_SESSIONS] = savings
except:
_LOGGER.debug('Failed to retrieve saving session information')


result = await async_refresh_saving_sessions(
current,
client,
account_id,
hass.data[DOMAIN][DATA_SAVING_SESSIONS] if DATA_SAVING_SESSIONS in hass.data[DOMAIN] else None,
hass.bus.async_fire
)

hass.data[DOMAIN][DATA_SAVING_SESSIONS] = result
return hass.data[DOMAIN][DATA_SAVING_SESSIONS]

hass.data[DOMAIN][DATA_SAVING_SESSIONS_COORDINATOR] = DataUpdateCoordinator(
hass,
_LOGGER,
name="saving_sessions",
name=f"{account_id}_saving_sessions",
update_method=async_update_saving_sessions,
# Because of how we're using the data, we'll update every minute, but we will only actually retrieve
# data every 30 minutes
Expand Down
26 changes: 9 additions & 17 deletions custom_components/octopus_energy/octoplus/__init__.py
Original file line number Diff line number Diff line change
@@ -1,28 +1,20 @@
def current_saving_sessions_event(current_date, events):
current_event = None
import datetime
from ..api_client.saving_sessions import SavingSession

def current_saving_sessions_event(current_date: datetime, events: list[SavingSession]) -> SavingSession or None:
if events is not None:
for event in events:
if (event["start"] <= current_date and event["end"] >= current_date):
current_event = {
"start": event["start"],
"end": event["end"],
"duration_in_minutes": (event["end"] - event["start"]).total_seconds() / 60
}
break
if (event.start <= current_date and event.end >= current_date):
return event

return current_event
return None

def get_next_saving_sessions_event(current_date, events):
def get_next_saving_sessions_event(current_date: datetime, events: list[SavingSession]) -> SavingSession or None:
next_event = None

if events is not None:
for event in events:
if event["start"] > current_date and (next_event == None or event["start"] < next_event["start"]):
next_event = {
"start": event["start"],
"end": event["end"],
"duration_in_minutes": (event["end"] - event["start"]).total_seconds() / 60
}
if event.start > current_date and (next_event == None or event.start < next_event.start):
next_event = event

return next_event
31 changes: 19 additions & 12 deletions custom_components/octopus_energy/octoplus/saving_sessions.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
)
from ..utils import account_id_to_unique_key
from ..api_client import OctopusEnergyApiClient
from ..coordinators.saving_sessions import SavingSessionsCoordinatorResult

_LOGGER = logging.getLogger(__name__)

Expand All @@ -34,8 +35,12 @@ def __init__(self, hass: HomeAssistant, coordinator, client: OctopusEnergyApiCli
self._state = None
self._events = []
self._attributes = {
"joined_events": [],
"next_joined_event_start": None
"current_joined_event_start": None,
"current_joined_event_end": None,
"current_joined_event_duration_in_minutes": None,
"next_joined_event_start": None,
"next_joined_event_end": None,
"next_joined_event_duration_in_minutes": None
}

self.entity_id = generate_entity_id("binary_sensor.{}", self.unique_id, hass=hass)
Expand Down Expand Up @@ -63,14 +68,16 @@ def extra_state_attributes(self):
@property
def is_on(self):
"""Determine if the user is in a saving session."""
saving_session = self.coordinator.data if self.coordinator is not None else None
if (saving_session is not None and "events" in saving_session):
self._events = saving_session["events"]
saving_session: SavingSessionsCoordinatorResult = self.coordinator.data if self.coordinator is not None else None
if (saving_session is not None):
self._events = saving_session.joined_events
else:
self._events = []

self._attributes = {
"joined_events": self._events,
"current_joined_event_start": None,
"current_joined_event_end": None,
"current_joined_event_duration_in_minutes": None,
"next_joined_event_start": None,
"next_joined_event_end": None,
"next_joined_event_duration_in_minutes": None
Expand All @@ -80,17 +87,17 @@ def is_on(self):
current_event = current_saving_sessions_event(current_date, self._events)
if (current_event is not None):
self._state = True
self._attributes["current_joined_event_start"] = current_event["start"]
self._attributes["current_joined_event_end"] = current_event["end"]
self._attributes["current_joined_event_duration_in_minutes"] = current_event["duration_in_minutes"]
self._attributes["current_joined_event_start"] = current_event.start
self._attributes["current_joined_event_end"] = current_event.end
self._attributes["current_joined_event_duration_in_minutes"] = current_event.duration_in_minutes
else:
self._state = False

next_event = get_next_saving_sessions_event(current_date, self._events)
if (next_event is not None):
self._attributes["next_joined_event_start"] = next_event["start"]
self._attributes["next_joined_event_end"] = next_event["end"]
self._attributes["next_joined_event_duration_in_minutes"] = next_event["duration_in_minutes"]
self._attributes["next_joined_event_start"] = next_event.start
self._attributes["next_joined_event_end"] = next_event.end
self._attributes["next_joined_event_duration_in_minutes"] = next_event.duration_in_minutes

return self._state

Expand Down
Loading

0 comments on commit b2b44b5

Please sign in to comment.