Skip to content

Commit aa4f08b

Browse files
Merge pull request #5010 from TCWORLD/octopus-lead-hours
Add Lead Hours Configuration for Octopus Saving Session Auto Join
2 parents 8285385 + fa12654 commit aa4f08b

4 files changed

Lines changed: 99 additions & 4 deletions

File tree

‎apps/predbat/config.py‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -889,6 +889,18 @@
889889
"type": "switch",
890890
"default": True,
891891
},
892+
{
893+
"name": "octopus_saving_auto_join_lead_hours",
894+
"friendly_name": "Octopus Saving Session Auto Join Lead Time",
895+
"type": "input_number",
896+
"min": 0,
897+
"max": 12,
898+
"step": 1,
899+
"unit": "hours",
900+
"icon": "mdi:clock-end",
901+
"enable": "octopus_saving_auto_join",
902+
"default": 0,
903+
},
892904
{
893905
"name": "octopus_intelligent_ignore_unplugged",
894906
"friendly_name": "Ignore Intelligent slots when car is unplugged",

‎apps/predbat/octopus.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3844,6 +3844,7 @@ def fetch_octopus_sessions(self, axle_sessions=None):
38443844
saving_rate = 200 # Default rate if not reported
38453845
octopoints_per_penny = self.get_arg("octopus_saving_session_octopoints_per_penny", 8) # Default 8 octopoints per penny
38463846
octopoints_min_threshold = self.get_arg("octopus_saving_session_min_octopoints_per_kwh", 0)
3847+
join_lead_hours = self.get_arg("octopus_saving_auto_join_lead_hours", 0)
38473848

38483849
joined_events = []
38493850
available_events = []
@@ -3910,6 +3911,10 @@ def fetch_octopus_sessions(self, axle_sessions=None):
39103911
if self._saving_event_conflicts_axle(start_time, end_time, axle_sessions):
39113912
self.log("Octopus: Skipping saving event code {} {}-{} - conflicts with an Axle VPP session".format(code, start_time.strftime("%a %d/%m %H:%M"), end_time.strftime("%H:%M")))
39123913
continue
3914+
# If a lead hours is set, skip joining session until we are within that many hours of the start time
3915+
if join_lead_hours and (start_time - timedelta(hours=join_lead_hours)) > self.now_utc:
3916+
self.log("Octopus: Delaying join of saving event code {} {}-{} - not within configured lead hours ({}) of start of event".format(code, start_time.strftime("%a %d/%m %H:%M"), end_time.strftime("%H:%M"), join_lead_hours))
3917+
continue
39133918
if code: # Join the new Octopus saving event and send an alert if successfully joined
39143919
self.log("Octopus: Joining Octopus saving event code {} {}-{} at rate {} p/kWh".format(code, start_time.strftime("%a %d/%m %H:%M"), end_time.strftime("%H:%M"), saving_rate))
39153920
entity_id_join = self.get_arg("octopus_saving_session_join", indirect=False)

‎apps/predbat/tests/test_saving_session.py‎

Lines changed: 80 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
# pylint: disable=attribute-defined-outside-init
1010
import yaml
1111
import json
12-
from datetime import timedelta
12+
from datetime import timedelta, datetime
1313
from ha import run_async
1414

1515

@@ -1035,6 +1035,11 @@ def test_saving_session_auto_join_toggle(my_predbat):
10351035
tz_offset = int(my_predbat.midnight_utc.tzinfo.utcoffset(my_predbat.midnight_utc).total_seconds() / 3600)
10361036
tz_offset = f"{tz_offset:02d}"
10371037

1038+
# Use a fixed time reference before event to allow testing of pre-event lead hours.
1039+
original_now_utc = my_predbat.now_utc
1040+
now = datetime.fromisoformat(f"{date_today}T15:45:00+{tz_offset}:00")
1041+
my_predbat.now_utc = now
1042+
10381043
session_binary = f"""
10391044
state: off
10401045
current_joined_event_start: null
@@ -1094,7 +1099,7 @@ def setup_items():
10941099
else:
10951100
print(" PASS: Join skipped when octopus_saving_auto_join is False")
10961101

1097-
# Test 2: auto-join enabled -> join proceeds
1102+
# Test 2: auto-join enabled, lead-hours unset default -> join proceeds
10981103
print(" Test 2: octopus_saving_auto_join=True allows the join")
10991104
setup_items()
11001105
my_predbat.expose_config("octopus_saving_auto_join", True, quiet=True)
@@ -1111,11 +1116,82 @@ def setup_items():
11111116
else:
11121117
print(" PASS: Join proceeds when octopus_saving_auto_join is True")
11131118

1114-
if not failed:
1115-
print("PASS: All auto-join toggle tests passed")
1119+
# Test 3: auto-join disabled, event within configured lead hours -> no join
1120+
print(" Test 3: octopus_saving_auto_join=False blocks the join despite time before event < octopus_saving_auto_join_lead_hours.")
1121+
setup_items()
1122+
my_predbat.expose_config("octopus_saving_auto_join", False, quiet=True)
1123+
my_predbat.expose_config("octopus_saving_auto_join_lead_hours", 3, quiet=True)
1124+
ha.service_store_enable = True
1125+
ha.service_store = []
1126+
my_predbat.fetch_octopus_sessions()
1127+
service_result = ha.get_service_store()
1128+
ha.service_store_enable = False
1129+
1130+
join_calls = [svc for svc in service_result if "join" in svc[0]]
1131+
if join_calls:
1132+
print(f"ERROR: Expected no join when auto-join disabled, got {join_calls}")
1133+
failed = True
1134+
else:
1135+
print(" PASS: Join skipped when octopus_saving_auto_join is False")
1136+
1137+
# Test 4: auto-join enabled, lead hours set to 0 (immediate) -> join proceeds
1138+
print(" Test 4: octopus_saving_auto_join=True with octopus_saving_auto_join_lead_hours=0 allows the join")
1139+
setup_items()
1140+
my_predbat.expose_config("octopus_saving_auto_join", True, quiet=True)
1141+
my_predbat.expose_config("octopus_saving_auto_join_lead_hours", 0, quiet=True)
1142+
ha.service_store_enable = True
1143+
ha.service_store = []
1144+
my_predbat.fetch_octopus_sessions()
1145+
service_result = ha.get_service_store()
1146+
ha.service_store_enable = False
1147+
1148+
join_calls = [svc for svc in service_result if "join" in svc[0]]
1149+
if len(join_calls) != 1:
1150+
print(f"ERROR: Expected 1 join when immediate auto-join enabled, got {len(join_calls)}: {service_result}")
1151+
failed = True
1152+
else:
1153+
print(" PASS: Join proceeds when octopus_saving_auto_join is True")
1154+
1155+
# Test 5: auto-join enabled, event outside configured lead hours -> no join
1156+
print(" Test 5: octopus_saving_auto_join=True with time before event > octopus_saving_auto_join_lead_hours skips the join")
1157+
setup_items()
1158+
my_predbat.expose_config("octopus_saving_auto_join", True, quiet=True)
1159+
my_predbat.expose_config("octopus_saving_auto_join_lead_hours", 2, quiet=True)
1160+
ha.service_store_enable = True
1161+
ha.service_store = []
1162+
my_predbat.fetch_octopus_sessions()
1163+
service_result = ha.get_service_store()
1164+
ha.service_store_enable = False
1165+
1166+
join_calls = [svc for svc in service_result if "join" in svc[0]]
1167+
if join_calls:
1168+
print(f"ERROR: Expected no join when auto-join enabled time before event > octopus_saving_auto_join_lead_hours, got {join_calls}")
1169+
failed = True
1170+
else:
1171+
print(" PASS: Join skipped when octopus_saving_auto_join is True and time before event > octopus_saving_auto_join_lead_hours")
1172+
1173+
# Test 6: auto-join enabled, event within configured lead hours -> join proceeds
1174+
print(" Test 6: octopus_saving_auto_join=True with time before event < octopus_saving_auto_join_lead_hours allows the join")
1175+
setup_items()
1176+
my_predbat.expose_config("octopus_saving_auto_join", True, quiet=True)
1177+
my_predbat.expose_config("octopus_saving_auto_join_lead_hours", 3, quiet=True)
1178+
ha.service_store_enable = True
1179+
ha.service_store = []
1180+
my_predbat.fetch_octopus_sessions()
1181+
service_result = ha.get_service_store()
1182+
ha.service_store_enable = False
1183+
1184+
join_calls = [svc for svc in service_result if "join" in svc[0]]
1185+
if len(join_calls) != 1:
1186+
print(f"ERROR: Expected 1 join when immediate auto-join enabled and time before event < octopus_saving_auto_join_lead_hours, got {len(join_calls)}: {service_result}")
1187+
failed = True
1188+
else:
1189+
print(" PASS: Join proceeds when octopus_saving_auto_join is True and time before event < octopus_saving_auto_join_lead_hours")
11161190

11171191
# Restore default state so we do not leak it to other tests
11181192
my_predbat.expose_config("octopus_saving_auto_join", True, quiet=True)
1193+
my_predbat.expose_config("octopus_saving_auto_join_lead_hours", 0, quiet=True)
1194+
my_predbat.now_utc = original_now_utc
11191195
my_predbat.octopus_last_joined_try = None
11201196

11211197
return failed

‎docs/energy-rates.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,8 @@ available sessions on your behalf.
171171

172172
If an available saving session overlaps an Axle VPP session (any overlap in time), Predbat will **not** auto-join the saving session, as the two events cannot both be honoured for the same period. The Axle session takes priority.
173173

174+
When auto-join is On, and there are no overlapping events, Predbat will join a saving session as soon as the event appears. In some cases this may not be desirable, for example when signed up to Axle short notice events, where a saving session could be announced before a conflicting Axle event. In such cases Predbat would have already joined the saving session, leading to potentially overlapping events. To resolve this, **octopus_saving_auto_join_lead_hours** can be configured to set how soon Predbat is allowed to join a saving session (default 0 = join immediately). Setting a value of e.g. "3" would mean Predbat will wait until 3 hours before the start of the saving session event before attempting to join, giving the opportunity for short notice events from Axle or other sources to be announced and considered first.
175+
174176
Predbat will also **not** auto-join a session whose reward rate doesn't exceed the **octopus_saving_session_min_octopoints_per_kwh** configuration item in `apps.yaml` (default 0 octopoints/kWh - joins any session with a positive reward). Raise this if you want to also skip low-value sessions you don't consider worth the disruption. The default of 0 additionally works around the Octopus Energy integration currently reporting some non-joinable events (national Power Up/free electricity slots) inside the same data set at 0 p/kWh - see [#4593](https://github.com/springfall2008/batpred/issues/4593).
175177

176178
In the Predbat plan, for joined saving sessions the energy rates for import and export will be overridden by adding the assumed saving rate to your normal rate.

0 commit comments

Comments
 (0)