diff --git a/apps/predbat/config.py b/apps/predbat/config.py index 5a77051f..a2597786 100644 --- a/apps/predbat/config.py +++ b/apps/predbat/config.py @@ -608,6 +608,18 @@ "default": False, "reset_inverter": True, }, + { + "name": "charge_low_power_margin", + "friendly_name": "Low power mode margin", + "type": "input_number", + "min": 0, + "max": 30, + "step": 5, + "unit": "minutes", + "icon": "mdi:seatbelt", + "enable": "set_charge_low_power", + "default": 10, + }, { "name": "set_reserve_enable", "friendly_name": "Set Reserve Enable", diff --git a/apps/predbat/execute.py b/apps/predbat/execute.py index 2957b3dd..b0fbfee8 100644 --- a/apps/predbat/execute.py +++ b/apps/predbat/execute.py @@ -116,9 +116,10 @@ def execute_plan(self): current_charge_rate = inverter.get_current_charge_rate() # Adjust charge rate if we are more than 10% out or we are going back to Max charge rate - if abs(new_charge_rate - current_charge_rate) > (0.1 * inverter.battery_rate_max_charge) or (new_charge_rate == inverter.battery_rate_max_charge * MINUTE_WATT): + max_rate = inverter.battery_rate_max_charge * MINUTE_WATT + if abs(new_charge_rate - current_charge_rate) > (0.1 * max_rate) or (new_charge_rate == max_rate): inverter.adjust_charge_rate(new_charge_rate) - resetCharge = False + resetCharge = False if inverter.inv_charge_discharge_with_rate: inverter.adjust_discharge_rate(0) diff --git a/apps/predbat/fetch.py b/apps/predbat/fetch.py index 57026756..1577864d 100644 --- a/apps/predbat/fetch.py +++ b/apps/predbat/fetch.py @@ -1773,6 +1773,7 @@ def fetch_config_options(self): self.set_export_freeze = self.get_arg("set_export_freeze") self.set_charge_freeze = self.get_arg("set_charge_freeze") self.set_charge_low_power = self.get_arg("set_charge_low_power") + self.charge_low_power_margin = self.get_arg("charge_low_power_margin") self.calculate_export_first = True self.set_status_notify = self.get_arg("set_status_notify") diff --git a/apps/predbat/prediction.py b/apps/predbat/prediction.py index 2ce35556..20269829 100644 --- a/apps/predbat/prediction.py +++ b/apps/predbat/prediction.py @@ -116,6 +116,7 @@ def __init__(self, base=None, pv_forecast_minute_step=None, pv_forecast_minute10 self.set_discharge_during_charge = base.set_discharge_during_charge self.set_read_only = base.set_read_only self.set_charge_low_power = base.set_charge_low_power + self.charge_low_power_margin = base.charge_low_power_margin self.car_charging_slots = base.car_charging_slots self.car_charging_limit = base.car_charging_limit self.car_charging_from_battery = base.car_charging_from_battery diff --git a/apps/predbat/unit_test.py b/apps/predbat/unit_test.py index 96757935..6439e1e9 100644 --- a/apps/predbat/unit_test.py +++ b/apps/predbat/unit_test.py @@ -744,6 +744,7 @@ def run_execute_test( set_charge_window=False, set_export_window=False, set_charge_low_power=False, + charge_low_power_margin=10, assert_charge_time_enable=False, assert_force_export=False, assert_pause_charge=False, @@ -778,6 +779,7 @@ def run_execute_test( my_predbat.num_cars = 1 my_predbat.inverter_hybrid = inverter_hybrid my_predbat.set_charge_low_power = set_charge_low_power + my_predbat.charge_low_power_margin = charge_low_power_margin if assert_immediate_soc_target is None: assert_immediate_soc_target = assert_soc_target @@ -1080,7 +1082,29 @@ def run_execute_tests(my_predbat): assert_status="Charging", assert_charge_start_time_minutes=-1, assert_charge_end_time_minutes=my_predbat.minutes_now + 60, - assert_charge_rate=500, + assert_charge_rate=600, # Within 10% + battery_max_rate=2000, + ) + if failed: + return failed + + # 60 minutes - 30 minute margin = 30 minutes to add 0.4kWh to each battery (x2 inverters) + # (60 / 20) * 400 = 1200 + failed |= run_execute_test( + my_predbat, + "charge_low_power2c", + charge_window_best=charge_window_best, + charge_limit_best=charge_limit_best, + assert_charge_time_enable=True, + soc_kw=9.2, + set_charge_window=True, + set_export_window=True, + set_charge_low_power=True, + charge_low_power_margin=40, + assert_status="Charging", + assert_charge_start_time_minutes=-1, + assert_charge_end_time_minutes=my_predbat.minutes_now + 60, + assert_charge_rate=1200, battery_max_rate=2000, ) if failed: @@ -1090,7 +1114,7 @@ def run_execute_tests(my_predbat): # (60 / 50) * 450 = 540 failed |= run_execute_test( my_predbat, - "charge_low_power2c", + "charge_low_power2d", charge_window_best=charge_window_best, charge_limit_best=charge_limit_best, assert_charge_time_enable=True, diff --git a/apps/predbat/utils.py b/apps/predbat/utils.py index 0ae0ad9b..efd8c4be 100644 --- a/apps/predbat/utils.py +++ b/apps/predbat/utils.py @@ -187,7 +187,9 @@ def find_charge_rate(model, minutes_now, soc, window, target_soc, max_rate, quie """ Find the lowest charge rate that fits the charge slow """ - margin = 10 + margin = model.charge_low_power_margin + target_soc = round(target_soc, 2) + if model.set_charge_low_power: minutes_left = window["end"] - minutes_now - margin @@ -208,21 +210,22 @@ def find_charge_rate(model, minutes_now, soc, window, target_soc, max_rate, quie # What's the lowest we could go? min_rate = charge_left / minutes_left + min_rate_w = int(min_rate * MINUTE_WATT) # Apply the curve at each rate to pick one that works rate_w = max_rate * MINUTE_WATT best_rate = max_rate while rate_w >= 400: rate = rate_w / MINUTE_WATT - if rate >= min_rate: + if rate_w >= min_rate_w: charge_now = soc minute = 0 - # Compute over the time period, include the completion time (margin was already counted) - for minute in range(0, minutes_left + PREDICT_STEP, PREDICT_STEP): + # Compute over the time period, include the completion time + for minute in range(0, minutes_left, PREDICT_STEP): rate_scale = get_charge_rate_curve(model, charge_now, rate) charge_amount = rate_scale * PREDICT_STEP * model.battery_loss charge_now += charge_amount - if charge_now >= target_soc: + if round(charge_now, 2) >= target_soc: best_rate = rate break rate_w -= 100.0