diff --git a/apps/predbat/unit_test.py b/apps/predbat/unit_test.py index 826217de..0d25c200 100644 --- a/apps/predbat/unit_test.py +++ b/apps/predbat/unit_test.py @@ -904,6 +904,7 @@ def run_execute_tests(my_predbat): charge_window_best7 = [{"start": my_predbat.minutes_now, "end": my_predbat.minutes_now + 23 * 60, "average": 1}] charge_window_best8 = [{"start": 0, "end": my_predbat.minutes_now + 12 * 60, "average": 1}] charge_window_best9 = [{"start": my_predbat.minutes_now + 60, "end": my_predbat.minutes_now + 90, "average": 1}] + charge_window_best_short = [{"start": my_predbat.minutes_now, "end": my_predbat.minutes_now + 15, "average": 1}] charge_limit_best = [10, 10] charge_limit_best2 = [5] charge_limit_best_frz = [1] @@ -1139,6 +1140,71 @@ def run_execute_tests(my_predbat): if failed: return failed + my_predbat.battery_charge_power_curve = { + 100: 0.50, + 99: 0.50, + 98: 0.50, + 97: 0.50, + 96: 0.50, + 95: 0.50, + 94: 1.00, + 93: 1.00, + 92: 1.00, + 91: 1.00, + 90: 1.00, + 89: 1.00, + 88: 1.00, + 87: 1.00, + 86: 1.00, + 85: 1.00, + } + + # 60 minutes - 10 minute margin = 50 minutes to add 0.75kWh to each battery (x2 inverters) + # (60 / 50) * 750 = 900 + # But with the low power curve it will go at half rate from 95% + + failed |= run_execute_test( + my_predbat, + "charge_low_power3a", + charge_window_best=charge_window_best, + charge_limit_best=charge_limit_best, + assert_charge_time_enable=True, + soc_kw=8.0, + set_charge_window=True, + set_export_window=True, + set_charge_low_power=True, + assert_status="Charging", + assert_charge_start_time_minutes=-1, + assert_charge_end_time_minutes=my_predbat.minutes_now + 60, + assert_charge_rate=1300, + battery_max_rate=2000, + ) + if failed: + return failed + + failed |= run_execute_test( + my_predbat, + "charge_low_power_short", + charge_window_best=charge_window_best_short, + charge_limit_best=charge_limit_best, + assert_charge_time_enable=True, + soc_kw=9.835, + set_charge_window=True, + set_export_window=True, + set_charge_low_power=True, + assert_status="Charging", + assert_charge_start_time_minutes=-1, + assert_charge_end_time_minutes=my_predbat.minutes_now + 15, + assert_charge_rate=2000, + battery_max_rate=2000, + ) + if failed: + return failed + return 1 + + # Reset curve + my_predbat.battery_charge_power_curve = {} + failed |= run_execute_test( my_predbat, "charge_long", diff --git a/apps/predbat/utils.py b/apps/predbat/utils.py index 2016e04c..488fc0ce 100644 --- a/apps/predbat/utils.py +++ b/apps/predbat/utils.py @@ -222,6 +222,8 @@ def find_charge_rate(minutes_now, soc, window, target_soc, max_rate, soc_max, ba # Apply the curve at each rate to pick one that works rate_w = max_rate * MINUTE_WATT best_rate = max_rate + highest_achievable_rate = 0 + while rate_w >= 400: rate = rate_w / MINUTE_WATT if rate_w >= min_rate_w: @@ -229,16 +231,27 @@ def find_charge_rate(minutes_now, soc, window, target_soc, max_rate, soc_max, ba minute = 0 # Compute over the time period, include the completion time for minute in range(0, minutes_left, PREDICT_STEP): - rate_scale = get_charge_rate_curve(charge_now, rate, soc_max, max_rate, battery_charge_power_curve, battery_rate_min) * battery_rate_max_scaling + rate_scale = get_charge_rate_curve(charge_now, rate, soc_max, max_rate, battery_charge_power_curve, battery_rate_min) + highest_achievable_rate = max(highest_achievable_rate, rate_scale) + rate_scale *= battery_rate_max_scaling charge_amount = rate_scale * PREDICT_STEP * battery_loss charge_now += charge_amount if round(charge_now, 2) >= target_soc: best_rate = rate break + # if log_to: + # log_to("Low Power mode: rate: {} minutes: {} SOC: {} Target SOC: {} Charge left: {} Charge now: {} Rate scale: {} Charge amount: {} Charge now: {} best rate: {} highest achievable_rate {}".format( + # rate * MINUTE_WATT, minute, soc, target_soc, charge_left, charge_now, rate_scale * MINUTE_WATT, charge_amount, charge_now, best_rate*MINUTE_WATT, highest_achievable_rate*MINUTE_WATT)) else: break rate_w -= 100.0 + # If we tried to select a rate which is actually faster than the highest achievable (due to being close to 100% SOC) then default to max rate + if best_rate < max_rate and best_rate >= highest_achievable_rate: + if log_to: + log_to("Low Power mode: best rate {} >= highest achievable rate {}, default to max rate".format(best_rate * MINUTE_WATT, highest_achievable_rate * MINUTE_WATT)) + best_rate = max_rate + if log_to: log_to( "Low Power mode: minutes left: {} absolute: {} SOC: {} Target SOC: {} Charge left: {} Max rate: {} Min rate: {} Best rate: {}".format(