Skip to content

Commit 5a1441a

Browse files
feat: Added calendars for representing free electricity and saving sessions. Existing binary sensors have been deprecated. See repair notice for more information (2 hours 15 mins dev time)
1 parent 77654fc commit 5a1441a

File tree

11 files changed

+376
-3
lines changed

11 files changed

+376
-3
lines changed
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# 0003 - Move to calendar entities for Octoplus events
2+
3+
## Status
4+
Accepted
5+
6+
## Context
7+
8+
Currently binary sensors are provided to indicate when a saving or free electricity session is active for the current account. This also provides attributes for current and next start/end times.
9+
10+
Since the introduction of the saving session binary sensor, calendar entities have received more love within Home Assistant and become the preferred way of showing events. These are better supports in UI automations as you can offset calendar events easily (e.g. reminder 10 minutes before) without having to do template gymnastics. The calendar view of Home Assistant is also used by house hold members who are not as involved as other members in things like wall tablets. A few users move the data from the sensor data into a local calendar to produce this.
11+
12+
This request has been made on a few occasions, below are some samples
13+
14+
* https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/issues/561#issuecomment-1826830172
15+
* https://github.com/BottlecapDave/HomeAssistant-OctopusEnergy/issues/1397
16+
17+
## Decision
18+
19+
With more people coming on board to Home Assistant who don't necessarily come from a technological background, the automation UI becoming the preferred way of creating automations and the calendar entity getting more love, it has been decided to convert the saving session and free electricity sessions into calendar entities.
20+
21+
The old sensors will continue to be available until **May 2026** when they will be removed, to ease with the transition.
22+
23+
## Consequences
24+
25+
### Positive
26+
- Automations around sessions will be easier via the calendar trigger
27+
- Past and present sessions will be easily viewable in the Home Assistant Calendar view
28+
- Standard approach for people used to calendar entities.
29+
30+
### Negative
31+
- Users using effected entities will need to update all references
32+
- Some short-term disruption may occur as users adapt to the new entity behaviour.
33+
- Event duration (e.g. 60 minutes) will require templating still

_docs/entities/octoplus.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,10 @@ Determines the current Octoplus points balance. This sensor will only be availab
1818

1919
## Saving Sessions
2020

21+
!!! warning
22+
23+
This sensor has been deprecated in favour of [Saving Session Calendar](#saving-sessions-calendar) and will be removed around **May 2026**
24+
2125
`binary_sensor.octopus_energy_{{ACCOUNT_ID}}_octoplus_saving_sessions`
2226

2327
Binary sensor to indicate if a saving session that the account has joined is active.
@@ -35,6 +39,20 @@ Binary sensor to indicate if a saving session that the account has joined is act
3539

3640
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.
3741

42+
## Saving Sessions Calendar
43+
44+
`calendar.octopus_energy_{{ACCOUNT_ID}}_octoplus_saving_sessions`
45+
46+
Calendar sensor to record saving sessions. Will be `on` when a saving session that the account has joined is active. Standard calendar attributes will indicate the current/next saving session.
47+
48+
!!! info
49+
50+
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.
51+
52+
!!! note
53+
54+
The events are supplied by OE API and does not store past events indefinitely. Past events could be removed without notice.
55+
3856
## Saving Session Events
3957

4058
`event.octopus_energy_{{ACCOUNT_ID}}_octoplus_saving_session_events`
@@ -121,6 +139,10 @@ Each item within `baselines` consists of the following attributes
121139

122140
## Free Electricity Sessions
123141

142+
!!! warning
143+
144+
This sensor has been deprecated in favour of [Free Electricity Sessions Calendar](#free-electricity-sessions-calendar) and will be removed around **May 2026**
145+
124146
`binary_sensor.octopus_energy_{{ACCOUNT_ID}}_octoplus_free_electricity_session`
125147

126148
Binary sensor to indicate if a free electricity session is active.
@@ -140,6 +162,22 @@ Binary sensor to indicate if a free electricity session is active.
140162
| `next_event_end` | `datetime` | The datetime the next free electricity session will end |
141163
| `next_event_duration_in_minutes` | `float` | The duration in minutes of the next free electricity session |
142164

165+
!!! info
166+
167+
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.
168+
169+
## Free Electricity Sessions Calendar
170+
171+
`calendar.octopus_energy_{{ACCOUNT_ID}}_octoplus_free_electricity_session`
172+
173+
Calendar sensor to record free electricity sessions. Will be `on` when a free electricity session is active. Standard calendar attributes will indicate the current/next saving session.
174+
175+
!!! note
176+
This will only be available if you have enrolled into Octoplus. Once enrolled, reload the integration to gain access to this sensor. This is only applicable if you have signed up to [free electricity sessions](https://octopus.energy/free-electricity/). This sensor uses public information supplied by https://github.com/BottlecapDave/OctopusEnergyApi.
177+
178+
!!! note
179+
This is [disabled by default](../faq.md#there-are-entities-that-are-disabled-why-are-they-disabled-and-how-do-i-enable-them).
180+
143181
!!! info
144182

145183
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.

custom_components/octopus_energy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@
8686
REPAIR_UNKNOWN_INTELLIGENT_PROVIDER
8787
)
8888

89-
ACCOUNT_PLATFORMS = ["sensor", "binary_sensor", "number", "switch", "text", "time", "event", "select", "climate", "water_heater"]
89+
ACCOUNT_PLATFORMS = ["sensor", "binary_sensor", "number", "switch", "text", "time", "event", "select", "climate", "water_heater", "calendar"]
9090
TARGET_RATE_PLATFORMS = ["binary_sensor"]
9191
COST_TRACKER_PLATFORMS = ["sensor"]
9292
TARIFF_COMPARISON_PLATFORMS = ["sensor"]

custom_components/octopus_energy/binary_sensor.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
DATA_ELECTRICITY_RATES_COORDINATOR_KEY,
4646
DATA_SAVING_SESSIONS_COORDINATOR,
4747
DATA_ACCOUNT,
48+
REPAIR_FREE_ELECTRICITY_SESSION_BINARY_SENSOR_DEPRECATED,
49+
REPAIR_SAVING_SESSION_BINARY_SENSOR_DEPRECATED,
4850
REPAIR_TARGET_RATE_REMOVAL_PROPOSAL
4951
)
5052

@@ -137,8 +139,27 @@ async def async_setup_main_sensors(hass, entry, async_add_entities):
137139
OctopusEnergyGreennessForecastHighlighted(hass, greenness_forecast_coordinator, account_id)
138140
]
139141

142+
ir.async_create_issue(
143+
hass,
144+
DOMAIN,
145+
REPAIR_SAVING_SESSION_BINARY_SENSOR_DEPRECATED,
146+
is_fixable=False,
147+
severity=ir.IssueSeverity.WARNING,
148+
learn_more_url="https://bottlecapdave.github.io/HomeAssistant-OctopusEnergy/architecture_decision_records/0003_move_to_calendar_entities_for_octoplus_events",
149+
translation_key="saving_session_binary_sensor_deprecated",
150+
)
151+
140152
if octoplus_enrolled:
141-
entities.append(OctopusEnergyFreeElectricitySessions(hass, free_electricity_session_coordinator, account_id))
153+
entities.append(OctopusEnergyFreeElectricitySessions(hass, free_electricity_session_coordinator, account_id))
154+
ir.async_create_issue(
155+
hass,
156+
DOMAIN,
157+
REPAIR_FREE_ELECTRICITY_SESSION_BINARY_SENSOR_DEPRECATED,
158+
is_fixable=False,
159+
severity=ir.IssueSeverity.WARNING,
160+
learn_more_url="https://bottlecapdave.github.io/HomeAssistant-OctopusEnergy/architecture_decision_records/0003_move_to_calendar_entities_for_octoplus_events",
161+
translation_key="free_electricity_session_binary_sensor_deprecated",
162+
)
142163

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

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import logging
2+
3+
from homeassistant.util.dt import (utcnow)
4+
5+
6+
from .const import (
7+
CONFIG_KIND,
8+
CONFIG_KIND_ACCOUNT,
9+
CONFIG_ACCOUNT_ID,
10+
DATA_FREE_ELECTRICITY_SESSIONS_COORDINATOR,
11+
DOMAIN,
12+
13+
DATA_SAVING_SESSIONS_COORDINATOR,
14+
DATA_ACCOUNT
15+
)
16+
17+
from .octoplus.free_electricity_sessions_calendar import OctopusEnergyFreeElectricitySessionsCalendar
18+
from .octoplus.saving_sessions_calendar import OctopusEnergySavingSessionsCalendar
19+
20+
_LOGGER = logging.getLogger(__name__)
21+
22+
async def async_setup_entry(hass, entry, async_add_entities):
23+
"""Setup sensors based on our entry"""
24+
25+
if entry.data[CONFIG_KIND] == CONFIG_KIND_ACCOUNT:
26+
await async_setup_main_sensors(hass, entry, async_add_entities)
27+
28+
return True
29+
30+
async def async_setup_main_sensors(hass, entry, async_add_entities):
31+
_LOGGER.debug('Setting up main sensors')
32+
config = dict(entry.data)
33+
34+
account_id = config[CONFIG_ACCOUNT_ID]
35+
account_result = hass.data[DOMAIN][account_id][DATA_ACCOUNT]
36+
account_info = account_result.account if account_result is not None else None
37+
octoplus_enrolled = account_info is not None and account_info["octoplus_enrolled"] == True
38+
39+
saving_session_coordinator = hass.data[DOMAIN][account_id][DATA_SAVING_SESSIONS_COORDINATOR]
40+
free_electricity_session_coordinator = hass.data[DOMAIN][account_id][DATA_FREE_ELECTRICITY_SESSIONS_COORDINATOR]
41+
42+
now = utcnow()
43+
entities = [
44+
OctopusEnergySavingSessionsCalendar(hass, saving_session_coordinator, account_id),
45+
]
46+
47+
if octoplus_enrolled:
48+
entities.append(OctopusEnergyFreeElectricitySessionsCalendar(hass, free_electricity_session_coordinator, account_id))
49+
50+
if len(entities) > 0:
51+
async_add_entities(entities)

custom_components/octopus_energy/const.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,8 @@
214214
REPAIR_INTELLIGENT_DEVICE_NOT_FOUND = "intelligent_device_not_found_{}"
215215
REPAIR_INTELLIGENT_DEVICE_CHANGED = "intelligent_device_changed_{}"
216216
REPAIR_TARIFF_RATES_EMPTY = "tariff_rates_empty_{}_{}"
217+
REPAIR_SAVING_SESSION_BINARY_SENSOR_DEPRECATED = "saving_session_binary_sensor_deprecated"
218+
REPAIR_FREE_ELECTRICITY_SESSION_BINARY_SENSOR_DEPRECATED = "saving_session_binary_sensor_deprecated"
217219

218220
# During BST, two records are returned before the rest of the data is available
219221
MINIMUM_CONSUMPTION_DATA_LENGTH = 3
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
from datetime import datetime
2+
import logging
3+
4+
from homeassistant.const import (
5+
STATE_UNAVAILABLE,
6+
STATE_UNKNOWN,
7+
)
8+
from homeassistant.core import HomeAssistant, callback
9+
from homeassistant.helpers.entity import generate_entity_id
10+
from homeassistant.util.dt import (utcnow)
11+
12+
from homeassistant.helpers.update_coordinator import (
13+
CoordinatorEntity
14+
)
15+
from homeassistant.components.calendar import (
16+
CalendarEntity,
17+
CalendarEvent,
18+
)
19+
from homeassistant.helpers.restore_state import RestoreEntity
20+
21+
from . import (
22+
current_octoplus_sessions_event,
23+
get_next_octoplus_sessions_event
24+
)
25+
26+
from ..utils.attributes import dict_to_typed_dict
27+
from ..coordinators.free_electricity_sessions import FreeElectricitySessionsCoordinatorResult
28+
29+
_LOGGER = logging.getLogger(__name__)
30+
31+
class OctopusEnergyFreeElectricitySessionsCalendar(CoordinatorEntity, CalendarEntity, RestoreEntity):
32+
"""Sensor for determining if a free electricity session is active."""
33+
34+
_unrecorded_attributes = frozenset({"data_last_retrieved"})
35+
36+
def __init__(self, hass: HomeAssistant, coordinator, account_id: str):
37+
"""Init sensor."""
38+
39+
CoordinatorEntity.__init__(self, coordinator)
40+
41+
self._account_id = account_id
42+
self._event = None
43+
self._events = []
44+
45+
self.entity_id = generate_entity_id("calendar.{}", self.unique_id, hass=hass)
46+
47+
@property
48+
def unique_id(self):
49+
"""The id of the sensor."""
50+
return f"octopus_energy_{self._account_id}_octoplus_free_electricity_session"
51+
52+
@property
53+
def name(self):
54+
"""Name of the sensor."""
55+
return f"Octoplus Free Electricity ({self._account_id})"
56+
57+
@property
58+
def entity_registry_enabled_default(self) -> bool:
59+
"""Return if the entity should be enabled when first added.
60+
61+
This only applies when fist added to the entity registry.
62+
"""
63+
return False
64+
65+
@property
66+
def event(self) -> CalendarEvent | None:
67+
"""Return the next upcoming event."""
68+
return self._event
69+
70+
@callback
71+
def _handle_coordinator_update(self) -> None:
72+
"""Determine if the user is in a free electricity session."""
73+
74+
free_electricity_session: FreeElectricitySessionsCoordinatorResult = self.coordinator.data if self.coordinator is not None else None
75+
if (free_electricity_session is not None):
76+
self._events = free_electricity_session.events
77+
else:
78+
self._events = []
79+
80+
current_date = utcnow()
81+
current_event = current_octoplus_sessions_event(current_date, self._events)
82+
if (current_event is not None):
83+
self._event = CalendarEvent(
84+
uid=current_event.code,
85+
summary="Octopus Energy Free Electricity",
86+
start=current_event.start,
87+
end=current_event.end,
88+
)
89+
else:
90+
next_event = get_next_octoplus_sessions_event(current_date, self._events)
91+
if (next_event is not None):
92+
self._event = CalendarEvent(
93+
uid=next_event.code,
94+
summary="Octopus Energy Free Electricity",
95+
start=next_event.start,
96+
end=next_event.end,
97+
)
98+
99+
super()._handle_coordinator_update()
100+
101+
async def async_get_events(
102+
self, hass, start_date: datetime, end_date: datetime
103+
) -> list[CalendarEvent]:
104+
"""Get all events in a specific time frame."""
105+
events = []
106+
if self._events is not None:
107+
for event in self._events:
108+
if event.start < end_date and event.end > start_date:
109+
events.append(CalendarEvent(
110+
uid=event.code,
111+
summary="Octopus Energy Free Electricity",
112+
start=event.start,
113+
end=event.end,
114+
))
115+
116+
return events

custom_components/octopus_energy/octoplus/saving_sessions.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ def unique_id(self):
6060
@property
6161
def name(self):
6262
"""Name of the sensor."""
63-
return f"Octoplus Saving Session ({self._account_id})"
63+
return f"Octoplus Saving Sessions ({self._account_id})"
6464

6565
@property
6666
def icon(self):

0 commit comments

Comments
 (0)