From 8aab1949d26901be4509c46efd72917ca7caedd9 Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 11:17:52 +0100 Subject: [PATCH 01/19] Fixing wrong debug msg: "hour" Changeing logic messages from hour to "slots". Adding a message to show interval at the beginning of evaluation --- src/batcontrol/core.py | 3 ++ src/batcontrol/logic/default.py | 78 +++++++++++++++++---------------- 2 files changed, 44 insertions(+), 37 deletions(-) diff --git a/src/batcontrol/core.py b/src/batcontrol/core.py index ba21793..ee47cd1 100644 --- a/src/batcontrol/core.py +++ b/src/batcontrol/core.py @@ -337,6 +337,9 @@ def handle_forecast_error(self): def run(self): """ Main calculation & control loop """ + logger.info('Running Batcontrol evaluation cycle') + logger.debug('Timeslots are in %d-minute intervals', self.time_resolution) + # Reset some values self.__reset_run_data() diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index 2329323..6a31b5a 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -1,5 +1,6 @@ import logging import datetime +import math import numpy as np from typing import Optional @@ -101,7 +102,7 @@ def calculate_inverter_mode(self, calc_input: CalculationInput, calc_timestamp = datetime.datetime.now().astimezone(self.timezone) # ensure availability of data - max_hour = min(len(net_consumption), len(prices)) + max_slot = min(len(net_consumption), len(prices)) if self.__is_discharge_allowed(calc_input, net_consumption, prices, calc_timestamp): inverter_control_settings.allow_discharge = True @@ -126,8 +127,8 @@ def calculate_inverter_mode(self, calc_input: CalculationInput, ) required_recharge_energy = self.__get_required_recharge_energy( calc_input, - net_consumption[:max_hour], - prices + net_consumption[:max_slot], + prices[:max_slot] ) else: logger.debug('Charging is NOT allowed, because SOC is above %.0f%%', @@ -220,15 +221,15 @@ def __is_discharge_allowed(self, calc_input: CalculationInput, self.calculation_output.min_dynamic_price_difference = min_dynamic_price_difference - max_hour = len(net_consumption) + max_slots = len(net_consumption) # relevant time range : until next recharge possibility - for h in range(1, max_hour): - future_price = prices[h] + for slot in range(1, max_slots): + future_price = prices[slot] if future_price <= current_price-min_dynamic_price_difference: - max_hour = h + max_slots = slot logger.debug( - "[Rule] Recharge possible in %d hours, limiting evaluation window.", - h) + "[Rule] Recharge possible in %d slots, limiting evaluation window.", + slot) logger.debug( "[Rule] Future price: %.3f < Current price: %.3f - dyn_price_diff. %.3f ", future_price, @@ -236,15 +237,18 @@ def __is_discharge_allowed(self, calc_input: CalculationInput, min_dynamic_price_difference ) break - dt = datetime.timedelta(hours=max_hour-1) + + display_minutes = (max_slots * self.interval_minutes) - self.interval_minutes + + dt = datetime.timedelta(minutes=display_minutes) t0 = calc_timestamp - t1 = t0+dt - last_hour = t1.astimezone(self.timezone).strftime("%H:59") + t1 = t0 + dt + last_time = t1.astimezone(self.timezone).strftime("%H:%M") logger.debug( - 'Evaluating next %d hours until %s', - max_hour, - last_hour + 'Evaluating next %d slots until %s', + max_slots, + last_time ) # distribute remaining energy consumption = np.array(net_consumption) @@ -253,45 +257,45 @@ def __is_discharge_allowed(self, calc_input: CalculationInput, production = -np.array(net_consumption) production[production < 0] = 0 - # get hours with higher price - higher_price_hours = [] - for h in range(max_hour): - future_price = prices[h] - # !!! different formula compared to detect relevant hours + # get slots with higher price + higher_price_slots = [] + for slot in range(max_slots): + future_price = prices[slot] + # !!! different formula compared to detect relevant slots if future_price > current_price: - higher_price_hours.append(h) + higher_price_slots.append(slot ) - higher_price_hours.sort() - higher_price_hours.reverse() + higher_price_slots.sort() + higher_price_slots.reverse() reserved_storage = 0 - for higher_price_hour in higher_price_hours: - if consumption[higher_price_hour] == 0: + for higher_price_slot in higher_price_slots: + if consumption[higher_price_slot] == 0: continue - required_energy = consumption[higher_price_hour] + required_energy = consumption[higher_price_slot] # correct reserved_storage with potential production - # start with latest hour - for hour in list(range(higher_price_hour))[::-1]: - if production[hour] == 0: + # start with latest slot + for slot in list(range(higher_price_slot))[::-1]: + if production[slot] == 0: continue - if production[hour] >= required_energy: - production[hour] -= required_energy + if production[slot] >= required_energy: + production[slot] -= required_energy required_energy = 0 break else: - required_energy -= production[hour] - production[hour] = 0 + required_energy -= production[slot] + production[slot ] = 0 # add_remaining required_energy to reserved_storage reserved_storage += required_energy self.calculation_output.reserved_energy = reserved_storage - if len(higher_price_hours) > 0: + if len(higher_price_slots) > 0: # This message is somehow confusing, because we are working with an # hour offset "the next 2 hours", but people may read "2 o'clock". - logger.debug("[Rule] Reserved Energy will be used in the next hours: %s", - higher_price_hours[::-1]) + logger.debug("[Rule] Reserved Energy will be used in the next slots: %s", + higher_price_slots[::-1]) logger.debug( "[Rule] Reserved Energy: %0.1f Wh. Usable in Battery: %0.1f Wh", reserved_storage, @@ -299,7 +303,7 @@ def __is_discharge_allowed(self, calc_input: CalculationInput, ) else: logger.debug("[Rule] No reserved energy required, because no " - "'high price' hours in evaluation window.") + "'high price' slots in evaluation window.") if calc_input.stored_usable_energy > reserved_storage: From bc8c8b4c44febe15e5d97269a5014af77d9a8f32 Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 11:21:23 +0100 Subject: [PATCH 02/19] Changing more messages hours -> slots --- src/batcontrol/logic/default.py | 40 ++++++++++++++++----------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index 6a31b5a..fdc54e9 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -336,7 +336,7 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , return: float (Energy in Wh) """ current_price = prices[0] - max_hour = len(net_consumption) + max_slot = len(net_consumption) consumption = np.array(net_consumption) consumption[consumption < 0] = 0 @@ -347,8 +347,8 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , current_price) # evaluation period until price is first time lower then current price - for h in range(1, max_hour): - future_price = prices[h] + for slots in range(1, max_slot): + future_price = prices[slots] found_lower_price = False # Soften the price difference to avoid too early charging if self.soften_price_difference_on_charging: @@ -359,40 +359,40 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , found_lower_price = future_price <= current_price if found_lower_price: - max_hour = h + max_slot = slots break - # get high price hours - high_price_hours = [] - for h in range(max_hour): - future_price = prices[h] + # get high price slots + high_price_slots = [] + for slots in range(max_slot): + future_price = prices[slots] if future_price > current_price+min_dynamic_price_difference: - high_price_hours.append(h) + high_price_slots.append(slots) # start with nearest hour - high_price_hours.sort() + high_price_slots.sort() required_energy = 0.0 - for high_price_hour in high_price_hours: - energy_to_shift = consumption[high_price_hour] + for high_price_slot in high_price_slots: + energy_to_shift = consumption[high_price_slot] # correct energy to shift with potential production # start with nearest hour - for hour in range(1, high_price_hour): - if production[hour] == 0: + for slot in range(1, high_price_slot): + if production[slot] == 0: continue - if production[hour] >= energy_to_shift: - production[hour] -= energy_to_shift + if production[slot] >= energy_to_shift: + production[slot] -= energy_to_shift energy_to_shift = 0 else: - energy_to_shift -= production[hour] - production[hour] = 0 + energy_to_shift -= production[slot] + production[slot ] = 0 # add_remaining energy to shift to recharge amount required_energy += energy_to_shift if required_energy > 0.0: - logger.debug("[Rule] Required Energy: %0.1f Wh is based on next 'high price' hours %s", + logger.debug("[Rule] Required Energy: %0.1f Wh is based on next 'high price' slots %s", required_energy, - high_price_hours + high_price_slots ) recharge_energy = required_energy-calc_input.stored_usable_energy logger.debug("[Rule] Stored usable Energy: %0.1f , Recharge Energy: %0.1f Wh", From ace0fbaf4a46765f14cc632452350575421bfc31 Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 11:21:47 +0100 Subject: [PATCH 03/19] Update docstring to replace "high price hours" with "high price slots" --- src/batcontrol/logic/default.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index fdc54e9..aaa6b12 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -328,10 +328,10 @@ def __is_discharge_allowed(self, calc_input: CalculationInput, # %% def __get_required_recharge_energy(self, calc_input: CalculationInput , net_consumption: list, prices: dict) -> float: - """ Calculate the required energy to shift toward high price hours. + """ Calculate the required energy to shift toward high price slots. If a recharge price window is detected, the energy required to - recharge the battery to the next high price hours is calculated. + recharge the battery to the next high price slots is calculated. return: float (Energy in Wh) """ From d6830143c435bc8b741a5cf898046cf8b7134fd3 Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 11:28:48 +0100 Subject: [PATCH 04/19] Get rid of excessive recharge below threshold mesage --- src/batcontrol/logic/default.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index aaa6b12..529713a 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -362,6 +362,12 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , max_slot = slots break + logger.debug( + "[Rule] Evaluation window for recharge energy until slot %d with price %0.3f", + max_slot, + prices[max_slot-1] + ) + # get high price slots high_price_slots = [] for slots in range(max_slot): @@ -369,14 +375,14 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , if future_price > current_price+min_dynamic_price_difference: high_price_slots.append(slots) - # start with nearest hour + # start with nearest slot high_price_slots.sort() required_energy = 0.0 for high_price_slot in high_price_slots: energy_to_shift = consumption[high_price_slot] # correct energy to shift with potential production - # start with nearest hour + # start with nearest slot for slot in range(1, high_price_slot): if production[slot] == 0: continue @@ -409,6 +415,8 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , "[Rule] No additional energy required, because stored energy is sufficient." ) recharge_energy = 0.0 + self.calculation_output.required_recharge_energy = recharge_energy + return recharge_energy if recharge_energy > free_capacity: recharge_energy = free_capacity From 9aa1ed74587ebe7c7ab76c6ea677ddb6aa678e65 Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 11:37:44 +0100 Subject: [PATCH 05/19] Fix refactor mistake --- src/batcontrol/logic/default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index 529713a..b2fd255 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -128,7 +128,7 @@ def calculate_inverter_mode(self, calc_input: CalculationInput, required_recharge_energy = self.__get_required_recharge_energy( calc_input, net_consumption[:max_slot], - prices[:max_slot] + prices ) else: logger.debug('Charging is NOT allowed, because SOC is above %.0f%%', From 61140a8fdd6a9b00bc5ff389c0d773cff55f3cef Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 11:59:16 +0100 Subject: [PATCH 06/19] Update src/batcontrol/logic/default.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/batcontrol/logic/default.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index b2fd255..75f7af8 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -292,8 +292,8 @@ def __is_discharge_allowed(self, calc_input: CalculationInput, self.calculation_output.reserved_energy = reserved_storage if len(higher_price_slots) > 0: - # This message is somehow confusing, because we are working with an - # hour offset "the next 2 hours", but people may read "2 o'clock". + # This message refers to relative slots (e.g. "next 2 slots"), + # not specific clock times (e.g. "at 2 o'clock"). logger.debug("[Rule] Reserved Energy will be used in the next slots: %s", higher_price_slots[::-1]) logger.debug( From 00059ad9494ccf444188f2207355545757c527c6 Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 11:59:27 +0100 Subject: [PATCH 07/19] Update src/batcontrol/logic/default.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/batcontrol/logic/default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index 75f7af8..a9620ee 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -391,7 +391,7 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , energy_to_shift = 0 else: energy_to_shift -= production[slot] - production[slot ] = 0 + production[slot] = 0 # add_remaining energy to shift to recharge amount required_energy += energy_to_shift From 9dae4c1827cd4a798acc7fe322ce8894d878229e Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 11:59:53 +0100 Subject: [PATCH 08/19] Update src/batcontrol/logic/default.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/batcontrol/logic/default.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index a9620ee..7c3beb0 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -1,6 +1,5 @@ import logging import datetime -import math import numpy as np from typing import Optional From 0b851e573b0c1d75452a777d8711858f7bfa8798 Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 12:00:23 +0100 Subject: [PATCH 09/19] Update src/batcontrol/logic/default.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/batcontrol/logic/default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index 7c3beb0..72a4508 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -284,7 +284,7 @@ def __is_discharge_allowed(self, calc_input: CalculationInput, break else: required_energy -= production[slot] - production[slot ] = 0 + production[slot] = 0 # add_remaining required_energy to reserved_storage reserved_storage += required_energy From 3b7694d591e00122d85749eb67c07978414c1f28 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 12:27:19 +0100 Subject: [PATCH 10/19] Fix PEP8 whitespace: remove extra spaces in parentheses (#279) * Initial plan * Fix PEP8 whitespace: remove extra spaces in append(slot) call Co-authored-by: MaStr <1036501+MaStr@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: MaStr <1036501+MaStr@users.noreply.github.com> --- src/batcontrol/logic/default.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index 72a4508..bf09296 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -262,7 +262,7 @@ def __is_discharge_allowed(self, calc_input: CalculationInput, future_price = prices[slot] # !!! different formula compared to detect relevant slots if future_price > current_price: - higher_price_slots.append(slot ) + higher_price_slots.append(slot) higher_price_slots.sort() higher_price_slots.reverse() From a3f906e3905173e916fdc383f2f504f053d75fb0 Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 12:28:24 +0100 Subject: [PATCH 11/19] Fix slot boundary alignment for evaluation window end-time log (#280) * Initial plan * Fix last_time calculation to align to slot boundary Co-authored-by: MaStr <1036501+MaStr@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: MaStr <1036501+MaStr@users.noreply.github.com> --- src/batcontrol/logic/default.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index bf09296..08903c7 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -237,12 +237,14 @@ def __is_discharge_allowed(self, calc_input: CalculationInput, ) break - display_minutes = (max_slots * self.interval_minutes) - self.interval_minutes - - dt = datetime.timedelta(minutes=display_minutes) - t0 = calc_timestamp - t1 = t0 + dt - last_time = t1.astimezone(self.timezone).strftime("%H:%M") + slot_start = calc_timestamp.replace( + minute=(calc_timestamp.minute // self.interval_minutes) * self.interval_minutes, + second=0, + microsecond=0 + ) + last_time = (slot_start + datetime.timedelta( + minutes=max_slots * self.interval_minutes + )).astimezone(self.timezone).strftime("%H:%M") logger.debug( 'Evaluating next %d slots until %s', From dbdf0ca8eda904c0e0f668205cf53c6cf9de5aeb Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 12:32:26 +0100 Subject: [PATCH 12/19] Add feedback --- src/batcontrol/logic/default.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index 08903c7..79b0dc8 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -371,10 +371,10 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , # get high price slots high_price_slots = [] - for slots in range(max_slot): - future_price = prices[slots] + for slot in range(max_slot): + future_price = prices[slot] if future_price > current_price+min_dynamic_price_difference: - high_price_slots.append(slots) + high_price_slots.append(slot) # start with nearest slot high_price_slots.sort() @@ -427,7 +427,8 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , if not self.common.is_charging_above_minimum(recharge_energy): recharge_energy = 0.0 else: - # We are adding that minimum charge energy here, so that we are not stuck between limits. + # We are adding that minimum charge energy here, so that we are not stuck + # between limits. recharge_energy = recharge_energy + self.common.min_charge_energy self.calculation_output.required_recharge_energy = recharge_energy From 193c5591e2eb9f1ab5e51ad1b0004dcd1cff9afa Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 12:33:44 +0100 Subject: [PATCH 13/19] Use singal slot in loop over plural form --- src/batcontrol/logic/default.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index 79b0dc8..3df89ec 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -348,8 +348,8 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , current_price) # evaluation period until price is first time lower then current price - for slots in range(1, max_slot): - future_price = prices[slots] + for slot in range(1, max_slot): + future_price = prices[slot] found_lower_price = False # Soften the price difference to avoid too early charging if self.soften_price_difference_on_charging: @@ -360,7 +360,7 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , found_lower_price = future_price <= current_price if found_lower_price: - max_slot = slots + max_slot = slot break logger.debug( From 5d207d859d77acfeb84d2c55b65294bd6b829a00 Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 12:38:58 +0100 Subject: [PATCH 14/19] Update src/batcontrol/core.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/batcontrol/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/batcontrol/core.py b/src/batcontrol/core.py index ee47cd1..3faed7d 100644 --- a/src/batcontrol/core.py +++ b/src/batcontrol/core.py @@ -337,7 +337,7 @@ def handle_forecast_error(self): def run(self): """ Main calculation & control loop """ - logger.info('Running Batcontrol evaluation cycle') + logger.debug('Running Batcontrol evaluation cycle') logger.debug('Timeslots are in %d-minute intervals', self.time_resolution) # Reset some values From fac3bf84c8d59e61aece22230047d0a6d19543ad Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 12:44:07 +0100 Subject: [PATCH 15/19] remove entry message on run() --- src/batcontrol/core.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/batcontrol/core.py b/src/batcontrol/core.py index 3faed7d..c84e527 100644 --- a/src/batcontrol/core.py +++ b/src/batcontrol/core.py @@ -337,7 +337,6 @@ def handle_forecast_error(self): def run(self): """ Main calculation & control loop """ - logger.debug('Running Batcontrol evaluation cycle') logger.debug('Timeslots are in %d-minute intervals', self.time_resolution) # Reset some values From e68b0d77f4ce55f2e38139143304602e0649d6ae Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 12:54:12 +0100 Subject: [PATCH 16/19] Some other cleanups and formatting issues --- src/batcontrol/logic/common.py | 13 +++++++------ src/batcontrol/logic/default.py | 3 ++- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/batcontrol/logic/common.py b/src/batcontrol/logic/common.py index 1f64a99..db4a254 100644 --- a/src/batcontrol/logic/common.py +++ b/src/batcontrol/logic/common.py @@ -72,7 +72,7 @@ def get_always_allow_discharge_limit(self) -> float: return self.always_allow_discharge_limit def is_discharge_always_allowed_soc(self, soc: float) -> bool: - """ Check if discharge is always allowed based on the state of charge (SOC). + """ Check if discharge is always allowed based on the state of charge (SOC). Args: soc (float): State of charge as a percentage (0-100). Returns: @@ -92,11 +92,11 @@ def is_discharge_always_allowed_capacity(self, capacity: float) -> bool: if capacity >= self.max_capacity * self.always_allow_discharge_limit: logger.debug( - 'Discharge is \'always allowed\' for current capacity: %s Wh', round(capacity,0)) + 'Discharge is \'always allowed\' for current capacity: %d Wh', round(capacity,0)) return True logger.debug( - 'Discharge is NOT \'always allowed\' for current capacity: %s Wh', round(capacity,0)) + 'Discharge is NOT \'always allowed\' for current capacity: %d Wh', round(capacity,0)) return False def is_charging_above_minimum(self, needed_energy: float) -> bool: @@ -109,7 +109,8 @@ def is_charging_above_minimum(self, needed_energy: float) -> bool: return True logger.debug( - 'Charging needed recharge energy is below threshold(%s): %s Wh', round(self.min_charge_energy,0), + 'Charging needed recharge energy is below threshold(%d): %d Wh', + round(self.min_charge_energy,0), round(needed_energy,0)) return False @@ -127,6 +128,6 @@ def calculate_charge_rate(self, charge_rate: float) -> int: MIN_CHARGE_RATE, adjusted_charge_rate) adjusted_charge_rate = MIN_CHARGE_RATE - adjusted_charge_rate = int(round(adjusted_charge_rate, 0)) - logger.debug('Adjusted charge rate: %s W', adjusted_charge_rate) + adjusted_charge_rate = int(round(adjusted_charge_rate, 0)) + logger.debug('Adjusted charge rate: %.1f W', adjusted_charge_rate) return adjusted_charge_rate diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index 3df89ec..c23c282 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -363,9 +363,10 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , max_slot = slot break + # As max_slot is 1 at minimum, no protection againt out of range needed. logger.debug( "[Rule] Evaluation window for recharge energy until slot %d with price %0.3f", - max_slot, + max_slot-1, prices[max_slot-1] ) From 32a1320b647bc211901cd3d3ec94108838a4337b Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 13:10:20 +0100 Subject: [PATCH 17/19] Review Feedack added --- src/batcontrol/logic/common.py | 10 +++++----- src/batcontrol/logic/default.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/batcontrol/logic/common.py b/src/batcontrol/logic/common.py index db4a254..07bbb99 100644 --- a/src/batcontrol/logic/common.py +++ b/src/batcontrol/logic/common.py @@ -92,11 +92,11 @@ def is_discharge_always_allowed_capacity(self, capacity: float) -> bool: if capacity >= self.max_capacity * self.always_allow_discharge_limit: logger.debug( - 'Discharge is \'always allowed\' for current capacity: %d Wh', round(capacity,0)) + 'Discharge is \'always allowed\' for current capacity: %.0f Wh', round(capacity,0)) return True logger.debug( - 'Discharge is NOT \'always allowed\' for current capacity: %d Wh', round(capacity,0)) + 'Discharge is NOT \'always allowed\' for current capacity: %.0f Wh', round(capacity,0)) return False def is_charging_above_minimum(self, needed_energy: float) -> bool: @@ -109,7 +109,7 @@ def is_charging_above_minimum(self, needed_energy: float) -> bool: return True logger.debug( - 'Charging needed recharge energy is below threshold(%d): %d Wh', + 'Charging needed recharge energy is below threshold(%.0f): %.0f Wh', round(self.min_charge_energy,0), round(needed_energy,0)) return False @@ -119,7 +119,7 @@ def calculate_charge_rate(self, charge_rate: float) -> int: Args: charge_rate (float): The initial charge rate in W. Returns: - float: The adjusted charge rate in W.""" + int: The adjusted charge rate in W.""" logger.debug('Calculating charge rate: %s', charge_rate) adjusted_charge_rate = charge_rate * self.charge_rate_multiplier if adjusted_charge_rate < MIN_CHARGE_RATE: @@ -129,5 +129,5 @@ def calculate_charge_rate(self, charge_rate: float) -> int: adjusted_charge_rate = MIN_CHARGE_RATE adjusted_charge_rate = int(round(adjusted_charge_rate, 0)) - logger.debug('Adjusted charge rate: %.1f W', adjusted_charge_rate) + logger.debug('Adjusted charge rate: %d W', adjusted_charge_rate) return adjusted_charge_rate diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index c23c282..5c2d729 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -363,7 +363,7 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , max_slot = slot break - # As max_slot is 1 at minimum, no protection againt out of range needed. + # As max_slot is 1 at minimum, no protection against out of range needed. logger.debug( "[Rule] Evaluation window for recharge energy until slot %d with price %0.3f", max_slot-1, From 237629251b983874e221b0abe684a0fa0d167227 Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 13:22:43 +0100 Subject: [PATCH 18/19] Review feedback --- src/batcontrol/logic/default.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/batcontrol/logic/default.py b/src/batcontrol/logic/default.py index 5c2d729..cd60d30 100644 --- a/src/batcontrol/logic/default.py +++ b/src/batcontrol/logic/default.py @@ -327,7 +327,7 @@ def __is_discharge_allowed(self, calc_input: CalculationInput, return False # %% - def __get_required_recharge_energy(self, calc_input: CalculationInput , + def __get_required_recharge_energy(self, calc_input: CalculationInput, net_consumption: list, prices: dict) -> float: """ Calculate the required energy to shift toward high price slots. @@ -408,11 +408,6 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , recharge_energy ) else: - recharge_energy = 0.0 - - free_capacity = calc_input.free_capacity - - if recharge_energy <= 0.0: logger.debug( "[Rule] No additional energy required, because stored energy is sufficient." ) @@ -420,6 +415,8 @@ def __get_required_recharge_energy(self, calc_input: CalculationInput , self.calculation_output.required_recharge_energy = recharge_energy return recharge_energy + free_capacity = calc_input.free_capacity + if recharge_energy > free_capacity: recharge_energy = free_capacity logger.debug( From 17138802672d34b96854dfe0ac400927a81a0f17 Mon Sep 17 00:00:00 2001 From: Matthias Strubel Date: Wed, 25 Feb 2026 13:29:53 +0100 Subject: [PATCH 19/19] Update src/batcontrol/logic/common.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- src/batcontrol/logic/common.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/batcontrol/logic/common.py b/src/batcontrol/logic/common.py index 07bbb99..93d6308 100644 --- a/src/batcontrol/logic/common.py +++ b/src/batcontrol/logic/common.py @@ -110,8 +110,8 @@ def is_charging_above_minimum(self, needed_energy: float) -> bool: logger.debug( 'Charging needed recharge energy is below threshold(%.0f): %.0f Wh', - round(self.min_charge_energy,0), - round(needed_energy,0)) + round(self.min_charge_energy, 0), + round(needed_energy, 0)) return False def calculate_charge_rate(self, charge_rate: float) -> int: