Skip to content

Commit

Permalink
feat: Added sensors for tracking free electricity sessions (3 hours d…
Browse files Browse the repository at this point in the history
…ev time)
  • Loading branch information
BottlecapDave authored Nov 2, 2024
1 parent 6403589 commit 98cbafb
Show file tree
Hide file tree
Showing 26 changed files with 863 additions and 91 deletions.
9 changes: 9 additions & 0 deletions _docs/entities/diagnostics.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,15 @@ This sensor states when the previous/current and next rate data was last retriev

This sensor states when saving sessions data was last retrieved.

!!! note
This is [disabled by default](../faq.md#there-are-entities-that-are-disabled-why-are-they-disabled-and-how-do-i-enable-them).

## Free Electricity Sessions Data Last Retrieved

`sensor.octopus_energy_{{ACCOUNT_ID}}_free_electricity_sessions_data_last_retrieved`

This sensor states when free electricity sessions data was last retrieved.

!!! note
This is [disabled by default](../faq.md#there-are-entities-that-are-disabled-why-are-they-disabled-and-how-do-i-enable-them).

Expand Down
117 changes: 115 additions & 2 deletions _docs/entities/octoplus.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ To support Octopus Energy's [octoplus programme](https://octopus.energy/octoplus

Determines the current Octoplus points balance. This sensor will only be available if you have enrolled on the octoplus programme.

!!! note
This will only be available if you have enrolled into Octoplus. Once enrolled, reload the integration to gain access to this sensor.

| Attribute | Type | Description |
|-----------|------|-------------|
| `redeemable_points` | `integer` | The number of points that can be redeemed into account credit |
Expand All @@ -17,7 +20,7 @@ Determines the current Octoplus points balance. This sensor will only be availab

`binary_sensor.octopus_energy_{{ACCOUNT_ID}}_octoplus_saving_sessions`

Binary sensor to indicate if a saving session that the account has joined is active. Also supplies the list of joined events including future events.
Binary sensor to indicate if a saving session that the account has joined is active.

| Attribute | Type | Description |
|-----------|------|-------------|
Expand All @@ -36,7 +39,7 @@ Binary sensor to indicate if a saving session that the account has joined is act

`event.octopus_energy_{{ACCOUNT_ID}}_octoplus_saving_session_events`

The state of this sensor states when the saving session events were last updated. The attributes of this sensor exposes the current day's rates.
The state of this sensor states when the saving session events were last updated. The attributes of this sensor exposes the joined and available saving sessions.

| Attribute | Type | Description |
|-----------|------|-------------|
Expand Down Expand Up @@ -89,10 +92,120 @@ You can use the [current period consumption](./electricity.md#current-interval-a
| `total_baseline` | `float` | The total baseline for the current saving session |
| `baselines` | `list` | The collection of baselines for the current saving session |

Each item within `consumption_items` consists of the following attributes

| Attribute | Type | Description |
|-----------|------|-------------|
| `start` | `datetime` | The start datetime the consumption period |
| `end` | `datetime` | The end datetime the consumption period |
| `consumption` | `float` | The total consumption within the period in kWh |

Each item within `baselines` consists of the following attributes

| Attribute | Type | Description |
|-----------|------|-------------|
| `start` | `datetime` | The start datetime the baseline period |
| `end` | `datetime` | The end datetime the baseline period |
| `baseline` | `float` | The consumption in kWh for the baseline period |
| `is_incomplete_calculation` | `bool` | Determines if the calculation is based on the full set or partial set of data |

!!! info

You can use the [data_last_retrieved sensor](./diagnostics.md#saving-sessions-data-last-retrieved) to determine when the underlying data was last retrieved from the OE servers.

## Free Electricity Sessions

`binary_sensor.octopus_energy_{{ACCOUNT_ID}}_octoplus_free_electricity_session`

Binary sensor to indicate if a free electricity session is active.

!!! warning

This sensor uses public information. However it is only applicable to your account if you have joined Octoplus and have signed up to [free electricity sessions](https://octopus.energy/free-electricity/). Once enrolled into Octoplus, reload the integration to gain access to this sensor.

!!! note
This is [disabled by default](../faq.md#there-are-entities-that-are-disabled-why-are-they-disabled-and-how-do-i-enable-them).

| Attribute | Type | Description |
|-----------|------|-------------|
| `current_event_start` | `datetime` | The datetime the current free electricity session started |
| `current_event_end` | `datetime` | The datetime the current free electricity session will end |
| `current_event_duration_in_minutes` | `float` | The duration in minutes of the current free electricity session |
| `next_event_start` | `datetime` | The datetime the next free electricity session will start |
| `next_event_end` | `datetime` | The datetime the next free electricity session will end |
| `next_event_duration_in_minutes` | `float` | The duration in minutes of the next free electricity session |

!!! info

You can use the [data_last_retrieved sensor](./diagnostics.md#free-electricity-sessions-data-last-retrieved) to determine when the underlying data was last retrieved from the OE servers.

## Free Electricity Session Events

`event.octopus_energy_{{ACCOUNT_ID}}_octoplus_free_electricity_session_events`

The state of this sensor states when the free electricity session events were last updated. The attributes of this sensor exposes the past, present and future free electricity sessions.

!!! note
This is [disabled by default](../faq.md#there-are-entities-that-are-disabled-why-are-they-disabled-and-how-do-i-enable-them).

| Attribute | Type | Description |
|-----------|------|-------------|
| `events` | `array` | The collection of free electricity events |

Each item in the `events` attribute will include the following attributes

| Attribute | Type | Description |
|-----------|------|-------------|
| `code` | `string` | The code of the event. |
| `start` | `datetime` | The date/time the event starts |
| `end` | `datetime` | The date/time the event starts |
| `duration_in_minutes` | `integer` | The duration of the event in minutes |

## Free Electricity Session Baseline

`sensor.octopus_energy_electricity_{{METER_SERIAL_NUMBER}}_{{MPAN_NUMBER}}_octoplus_free_electricity_session_baseline`

This will indicate the baseline consumption that you need to be above for the current 30 minute period of the current free electricity session or the first 30 minute period of the next free electricity session.

You can use the [current period consumption](./electricity.md#current-interval-accumulative-consumption) sensor (if available) to see how on track you are.

!!! note
This is [disabled by default](../faq.md#there-are-entities-that-are-disabled-why-are-they-disabled-and-how-do-i-enable-them).

!!! info

An export variant of this sensor exists for export based meters.

| Attribute | Type | Description |
|-----------|------|-------------|
| `start` | `datetime` | The start datetime the current baseline applies |
| `end` | `datetime` | The end datetime the current baseline applies |
| `is_incomplete_calculation` | `bool` | Determines if the calculation is based on the full set or partial set of data |
| `consumption_items` | `list` | The consumption that was used to calculate the baselines |
| `total_baseline` | `float` | The total baseline for the current saving session |
| `baselines` | `list` | The collection of baselines for the current saving session |

Each item within `consumption_items` consists of the following attributes

| Attribute | Type | Description |
|-----------|------|-------------|
| `start` | `datetime` | The start datetime the consumption period |
| `end` | `datetime` | The end datetime the consumption period |
| `consumption` | `float` | The total consumption within the period in kWh |

Each item within `baselines` consists of the following attributes

| Attribute | Type | Description |
|-----------|------|-------------|
| `start` | `datetime` | The start datetime the baseline period |
| `end` | `datetime` | The end datetime the baseline period |
| `baseline` | `float` | The consumption in kWh for the baseline period |
| `is_incomplete_calculation` | `bool` | Determines if the calculation is based on the full set or partial set of data |

!!! info

You can use the [data_last_retrieved sensor](./diagnostics.md#free-electricity-sessions-data-last-retrieved) to determine when the underlying data was last retrieved from the OE servers.

## Services

There are some services available relating to these entities that you might find useful. They can be found in the [services docs](../services.md).
Expand Down
3 changes: 3 additions & 0 deletions custom_components/octopus_energy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from .coordinators.intelligent_settings import async_setup_intelligent_settings_coordinator
from .coordinators.electricity_rates import async_setup_electricity_rates_coordinator
from .coordinators.saving_sessions import async_setup_saving_sessions_coordinators
from .coordinators.free_electricity_sessions import async_setup_free_electricity_sessions_coordinators
from .coordinators.greenness_forecast import async_setup_greenness_forecast_coordinator
from .statistics import get_statistic_ids_to_remove
from .intelligent import get_intelligent_features, is_intelligent_product, mock_intelligent_device
Expand Down Expand Up @@ -434,6 +435,8 @@ async def async_setup_dependencies(hass, config):

await async_setup_saving_sessions_coordinators(hass, account_id)

await async_setup_free_electricity_sessions_coordinators(hass, account_id)

await async_setup_greenness_forecast_coordinator(hass, account_id)

async def options_update_listener(hass, entry):
Expand Down
28 changes: 28 additions & 0 deletions custom_components/octopus_energy/api_client/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .saving_sessions import JoinSavingSessionResponse, SavingSession, SavingSessionsResponse
from .wheel_of_fortune import WheelOfFortuneSpinsResponse
from .greenness_forecast import GreennessForecast
from .free_electricity_sessions import FreeElectricitySession, FreeElectricitySessionsResponse

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -897,6 +898,33 @@ async def async_get_saving_sessions(self, account_id: str) -> SavingSessionsResp

return None

async def async_get_free_electricity_sessions(self, account_id: str) -> FreeElectricitySessionsResponse:
"""Get the user's free electricity sessions"""

try:
client = self._create_client_session()
url = f'https://oe-api.davidskendall.co.uk/free_electricity.json'
# Get account response
payload = { }
headers = { }
async with client.get(url, json=payload, headers=headers) as response:
response_body = await self.__async_read_response__(response, url)

if (response_body is not None and "data" in response_body):
return FreeElectricitySessionsResponse(list(map(
lambda ev: FreeElectricitySession(
ev["code"],
as_utc(parse_datetime(ev["start"])),
as_utc(parse_datetime(ev["end"]))),
response_body["data"])))
else:
_LOGGER.error("Failed to retrieve free electricity sessions")
except TimeoutError:
_LOGGER.warning(f'Failed to connect. Timeout of {self._timeout} exceeded.')
raise TimeoutException()

return None

async def async_get_octoplus_points(self):
"""Get the user's octoplus points"""
await self.async_refresh_token()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
from datetime import datetime

from .octoplus_session import BaseOctoplusSession

class FreeElectricitySession(BaseOctoplusSession):
def __init__(
self,
code: str,
start: datetime,
end: datetime
):
BaseOctoplusSession.__init__(self, code, start, end)

class FreeElectricitySessionsResponse:
data: list[FreeElectricitySession]

def __init__(
self,
data: list[FreeElectricitySession]
):
self.data = data
19 changes: 19 additions & 0 deletions custom_components/octopus_energy/api_client/octoplus_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from datetime import datetime


class BaseOctoplusSession:
code: str
start: datetime
end: datetime
duration_in_minutes: int

def __init__(
self,
code: str,
start: datetime,
end: datetime
):
self.code = code
self.start = start
self.end = end
self.duration_in_minutes = (end - start).total_seconds() / 60
13 changes: 4 additions & 9 deletions custom_components/octopus_energy/api_client/saving_sessions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from datetime import datetime

from .octoplus_session import BaseOctoplusSession

class JoinSavingSessionResponse:
is_successful: bool
errors: list[str]
Expand All @@ -12,13 +14,9 @@ def __init__(
self.is_successful = is_successful
self.errors = errors

class SavingSession:
class SavingSession(BaseOctoplusSession):
id: str
code: str
start: datetime
end: datetime
octopoints: int
duration_in_minutes: int

def __init__(
self,
Expand All @@ -28,12 +26,9 @@ def __init__(
end: datetime,
octopoints: int
):
BaseOctoplusSession.__init__(self, code, start, end)
self.id = id
self.code = code
self.start = start
self.end = end
self.octopoints = octopoints
self.duration_in_minutes = (end - start).total_seconds() / 60

class SavingSessionsResponse:
available_events: list[SavingSession]
Expand Down
8 changes: 8 additions & 0 deletions custom_components/octopus_energy/binary_sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,15 @@
from .intelligent import get_intelligent_features
from .api_client.intelligent_device import IntelligentDevice
from .target_rates.rolling_target_rate import OctopusEnergyRollingTargetRate
from .octoplus.free_electricity_sessions import OctopusEnergyFreeElectricitySessions

from .const import (
CONFIG_KIND,
CONFIG_KIND_ACCOUNT,
CONFIG_KIND_ROLLING_TARGET_RATE,
CONFIG_KIND_TARGET_RATE,
CONFIG_ACCOUNT_ID,
DATA_FREE_ELECTRICITY_SESSIONS_COORDINATOR,
DATA_GREENNESS_FORECAST_COORDINATOR,
DATA_INTELLIGENT_DEVICE,
DATA_INTELLIGENT_DISPATCHES_COORDINATOR,
Expand Down Expand Up @@ -103,15 +105,21 @@ async def async_setup_main_sensors(hass, entry, async_add_entities):
account_id = config[CONFIG_ACCOUNT_ID]
account_result = hass.data[DOMAIN][account_id][DATA_ACCOUNT]
account_info = account_result.account if account_result is not None else None
octoplus_enrolled = account_info is not None and account_info["octoplus_enrolled"] == True

saving_session_coordinator = hass.data[DOMAIN][account_id][DATA_SAVING_SESSIONS_COORDINATOR]
greenness_forecast_coordinator = hass.data[DOMAIN][account_id][DATA_GREENNESS_FORECAST_COORDINATOR]
free_electricity_session_coordinator = hass.data[DOMAIN][account_id][DATA_FREE_ELECTRICITY_SESSIONS_COORDINATOR]

now = utcnow()
entities = [
OctopusEnergySavingSessions(hass, saving_session_coordinator, account_id),
OctopusEnergyGreennessForecastHighlighted(hass, greenness_forecast_coordinator, account_id)
]

if octoplus_enrolled:
entities.append(OctopusEnergyFreeElectricitySessions(hass, free_electricity_session_coordinator, account_id))

if len(account_info["electricity_meter_points"]) > 0:

for point in account_info["electricity_meter_points"]:
Expand Down
6 changes: 6 additions & 0 deletions custom_components/octopus_energy/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
REFRESH_RATE_IN_MINUTES_PREVIOUS_CONSUMPTION = 30
REFRESH_RATE_IN_MINUTES_STANDING_CHARGE = 60
REFRESH_RATE_IN_MINUTES_OCTOPLUS_SAVING_SESSIONS = 15
REFRESH_RATE_IN_MINUTES_OCTOPLUS_FREE_ELECTRICITY_SESSIONS = 60
REFRESH_RATE_IN_MINUTES_OCTOPLUS_SAVING_SESSION_TARGET = 60
REFRESH_RATE_IN_MINUTES_OCTOPLUS_WHEEL_OF_FORTUNE = 60
REFRESH_RATE_IN_MINUTES_OCTOPLUS_POINTS = 60
Expand Down Expand Up @@ -132,6 +133,8 @@
DATA_GREENNESS_FORECAST = "GREENNESS_FORECAST"
DATA_PREVIOUS_CONSUMPTION_COORDINATOR_KEY = "DATA_PREVIOUS_CONSUMPTION_AND_COST_COORDINATOR_{}_{}"
DATA_HOME_PRO_CURRENT_CONSUMPTION_KEY = "HOME_PRO_CURRENT_CONSUMPTION_{}"
DATA_FREE_ELECTRICITY_SESSIONS = "FREE_ELECTRICITY_SESSIONS"
DATA_FREE_ELECTRICITY_SESSIONS_COORDINATOR = "FREE_ELECTRICITY_SESSIONS_COORDINATOR"

DATA_SAVING_SESSIONS_FORCE_UPDATE = "SAVING_SESSIONS_FORCE_UPDATE"

Expand Down Expand Up @@ -192,6 +195,9 @@
EVENT_NEW_SAVING_SESSION = "octopus_energy_new_octoplus_saving_session"
EVENT_ALL_SAVING_SESSIONS = "octopus_energy_all_octoplus_saving_sessions"

EVENT_NEW_FREE_ELECTRICITY_SESSION = "octopus_energy_new_octoplus_free_electricity_session"
EVENT_ALL_FREE_ELECTRICITY_SESSIONS = "octopus_energy_all_octoplus_free_electricity_sessions"

REPAIR_UNIQUE_RATES_CHANGED_KEY = "electricity_unique_rates_updated_{}"
REPAIR_INVALID_API_KEY = "invalid_api_key_{}"
REPAIR_ACCOUNT_NOT_FOUND = "account_not_found_{}"
Expand Down
Loading

1 comment on commit 98cbafb

@raldred
Copy link

@raldred raldred commented on 98cbafb Nov 7, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A decent amount of work gone into this thank you @BottlecapDave

Please sign in to comment.