Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 21 additions & 1 deletion src/octosynk/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,18 @@ def get_time_env(key: str, default: str) -> time:
return None


def get_effective_off_peak_start(dispatches: list[octopus.Dispatch], off_peak_start: time) -> time:
"""Return the earliest contiguous cheap-rate start leading into off_peak_start.

If an Octopus dispatch ends exactly at off_peak_start, its local start time
is used as the effective off-peak start for discharge calculations.
"""
for dispatch in dispatches:
if dispatch.end_datetime_utc.astimezone().time() == off_peak_start:
return dispatch.start_datetime_utc.astimezone().time()
return off_peak_start


def dispatches_to_transitions(dispatches: list[octopus.Dispatch]) -> list[Transition]:
transitions: list[Transition] = []
for dispatch in dispatches:
Expand Down Expand Up @@ -141,14 +153,22 @@ def run():

sun_client = SunsynkClient(config)

eff_off_peak_start = get_effective_off_peak_start(dispatches, config.off_peak_start_time)
if eff_off_peak_start != config.off_peak_start_time:
logger.info(
"Effective off-peak start extended by contiguous dispatch",
effective=eff_off_peak_start,
configured=config.off_peak_start_time,
)

discharge_start = None
if config.battery_capacity_kwh:
current_soc = sun_client.get_battery_soc()
soc_to_discharge = current_soc - config.soc_min
if soc_to_discharge > 0:
energy_kwh = (soc_to_discharge / 100) * config.battery_capacity_kwh
duration = timedelta(hours=energy_kwh / (config.max_power_watts / 1000))
off_peak_dt = datetime.combine(date.today(), config.off_peak_start_time)
off_peak_dt = datetime.combine(date.today(), eff_off_peak_start)
calculated_start = (off_peak_dt - duration).time()
now = datetime.now().time()
if calculated_start <= now:
Expand Down
30 changes: 30 additions & 0 deletions src/tests/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from datetime import datetime, time, timezone

from octosynk.app import get_effective_off_peak_start
from octosynk.octopus import Dispatch


def utc(hour: int, minute: int = 0) -> datetime:
return datetime(2024, 1, 1, hour, minute, tzinfo=timezone.utc)


OFF_PEAK_START = time(23, 30)


def test_no_dispatches_returns_configured_start():
assert get_effective_off_peak_start([], OFF_PEAK_START) == OFF_PEAK_START


def test_contiguous_dispatch_extends_start():
dispatches = [Dispatch(start_datetime_utc=utc(22), end_datetime_utc=utc(23, 30))]
assert get_effective_off_peak_start(dispatches, OFF_PEAK_START) == time(22, 0)


def test_non_contiguous_dispatch_unchanged():
dispatches = [Dispatch(start_datetime_utc=utc(21), end_datetime_utc=utc(22))]
assert get_effective_off_peak_start(dispatches, OFF_PEAK_START) == OFF_PEAK_START


def test_dispatch_ending_before_off_peak_unchanged():
dispatches = [Dispatch(start_datetime_utc=utc(20), end_datetime_utc=utc(23))]
assert get_effective_off_peak_start(dispatches, OFF_PEAK_START) == OFF_PEAK_START
Loading