Skip to content

Commit b22e5bd

Browse files
committed
WIP: Handle the state that it is not possible to reach the next price minimum
1 parent c4ea26b commit b22e5bd

File tree

2 files changed

+82
-2
lines changed

2 files changed

+82
-2
lines changed

source/inverter_charge_controller.py

+29-2
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,13 @@ def _calculate_target_soc(
245245
"--> Proceeding with normal operation to determine the target state of charge"
246246
)
247247
else:
248-
# TODO: What to do in this case?
249-
pass
248+
self.log.info(
249+
f"It is not possible to reach the next price minimum with charging to {soc_full} "
250+
"--> Will determine the optimal points in time for charging around the price spike"
251+
)
252+
# This method charges the inverter itself and thus does not need the normal flow of operation
253+
self._handle_next_price_minimum_is_unreachable()
254+
return None
250255

251256
if current_energy_rate > self.next_price_minimum:
252257
self.log.info(
@@ -285,6 +290,28 @@ def _calculate_target_soc(
285290

286291
return charging_target_soc
287292

293+
def _handle_next_price_minimum_is_unreachable(self) -> None:
294+
soc_full = StateOfCharge.from_percentage(100)
295+
296+
upcoming_energy_rates = self._get_upcoming_energy_rates()
297+
energy_rate_before_price_rises_over_average, energy_rate_after_price_drops_after_average = (
298+
self.tibber_api_handler.get_energy_rate_before_and_after_the_price_is_higher_than_the_average_until_timestamp(
299+
upcoming_energy_rates, self.next_price_minimum.timestamp
300+
)
301+
)
302+
self.log.info(
303+
f"The last energy rate before the price rises over the average is "
304+
f"{energy_rate_before_price_rises_over_average}. "
305+
f"The first energy rate after the price drops is {energy_rate_after_price_drops_after_average}."
306+
f"--> Will charge now to {soc_full} and then wait until "
307+
f"{energy_rate_before_price_rises_over_average.timestamp}"
308+
)
309+
self.handle_charging(soc_full)
310+
self.log.info(f"Waiting until {energy_rate_before_price_rises_over_average.timestamp}")
311+
pause.until(energy_rate_before_price_rises_over_average.timestamp)
312+
313+
# TODO: Continue here
314+
288315
def handle_charging(self, charging_target_soc: StateOfCharge) -> None:
289316
"""
290317
Handles the charging process. This involves getting the energy bought before charging, charging the inverter,

source/tibber_api_handler.py

+53
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,59 @@ def _check_if_minimum_is_at_end_of_day_and_energy_rates_of_tomorrow_are_unavaila
292292

293293
return is_price_minimum_near_end_of_day and are_tomorrows_rates_unavailable
294294

295+
def get_energy_rate_before_and_after_the_price_is_higher_than_the_average_until_timestamp(
296+
self, upcoming_energy_rates: list[EnergyRate], ending_timestamp: datetime
297+
) -> tuple[EnergyRate, EnergyRate]:
298+
"""
299+
Identifies two specific energy rates from a list of upcoming energy rates up to a given timestamp:
300+
the last rate before the price is higher than the average and the first rate
301+
302+
Args:
303+
upcoming_energy_rates (list[EnergyRate]): The list of upcoming energy rates.
304+
ending_timestamp (datetime): A timestamp limiting the processing to only consider energy rates up to and
305+
including this time.
306+
307+
Returns:
308+
tuple[EnergyRate, EnergyRate]: A tuple containing:
309+
- The energy rate before the price is higher than the average price up to the provided timestamp.
310+
- The energy rate after the price is higher than the average price up to the provided timestamp.
311+
"""
312+
average_price = self._get_average_price_of_energy_rates(upcoming_energy_rates)
313+
self.log.debug(f"The average price of all upcoming energy rates is {average_price}")
314+
315+
upcoming_energy_rates_until_ending_timestamp = [
316+
energy_rate for energy_rate in upcoming_energy_rates if energy_rate.timestamp <= ending_timestamp
317+
]
318+
319+
self.log.trace("Determining the last energy rate before the price is higher than the average price...")
320+
energy_rate_before_the_price_is_higher_than_the_average = upcoming_energy_rates_until_ending_timestamp[0]
321+
for energy_rate in upcoming_energy_rates_until_ending_timestamp:
322+
if energy_rate.rate < average_price:
323+
self.log.trace(f"The energy rate {energy_rate} is cheaper than the average price")
324+
energy_rate_before_the_price_is_higher_than_the_average = energy_rate
325+
else:
326+
self.log.trace(f"The energy rate {energy_rate} is more expensive than the average price")
327+
break
328+
329+
self.log.trace("Determining the first energy rate after the price was higher than the average price...")
330+
energy_rate_after_the_price_is_higher_than_the_average = upcoming_energy_rates_until_ending_timestamp[-1]
331+
for energy_rate in reversed(upcoming_energy_rates_until_ending_timestamp):
332+
if energy_rate.rate < average_price:
333+
self.log.trace(f"The energy rate {energy_rate} is cheaper than the average price")
334+
energy_rate_after_the_price_is_higher_than_the_average = energy_rate
335+
else:
336+
self.log.trace(f"The energy rate {energy_rate} is more expensive than the average price")
337+
break
338+
339+
return (
340+
energy_rate_before_the_price_is_higher_than_the_average,
341+
energy_rate_after_the_price_is_higher_than_the_average,
342+
)
343+
344+
@staticmethod
345+
def _get_average_price_of_energy_rates(energy_rates: list[EnergyRate]) -> float:
346+
return sum(energy_rate.rate for energy_rate in energy_rates) / len(energy_rates)
347+
295348
def write_energy_rates_to_database(self, energy_rates: list[EnergyRate]) -> None:
296349
"""
297350
Writes the list of energy rates to the database while avoiding duplication of already existing data.

0 commit comments

Comments
 (0)