diff --git a/apps/predbat/execute.py b/apps/predbat/execute.py index 63360287..f24eedc9 100644 --- a/apps/predbat/execute.py +++ b/apps/predbat/execute.py @@ -199,6 +199,7 @@ def execute_plan(self): self.log("Disabling discharge during charge due to set_discharge_during_charge being False") isCharging = True + self.isCharging_Target = self.charge_limit_best[0] else: # Configure the charge window start/end times if in the time window to set them if (self.minutes_now < minutes_end) and ((minutes_start - self.minutes_now) <= self.set_window_minutes): @@ -289,6 +290,7 @@ def execute_plan(self): inverter.adjust_charge_rate(0) resetCharge = False isExporting = True + self.isExporting_Target = self.export_limits_best[0] status = "Exporting" status_extra = " target {}%-{}%".format(inverter.soc_percent, self.export_limits_best[0]) @@ -312,6 +314,7 @@ def execute_plan(self): status = "Freeze exporting" status_extra = " current SoC {}%".format(inverter.soc_percent) # Discharge limit (99) is meaningless when Freeze Exporting so don't display it isExporting = True + self.isExporting_Target = self.export_limits_best[0] inverter.adjust_export_immediate(inverter.soc_percent, freeze=True) else: status = "Hold exporting" @@ -536,7 +539,7 @@ def fetch_inverter_data(self): self.charge_window = [] self.export_window = [] self.export_limits = [] - self.current_charge_limit = 0.0 + self.current_charge_limit_kwh = 0.0 self.soc_kw = 0.0 self.soc_max = 0.0 self.reserve = 0.0 @@ -575,7 +578,6 @@ def fetch_inverter_data(self): # As the inverters will run in lockstep, we will initially look at the programming of the first enabled one for the current window setting if not found_first: found_first = True - self.current_charge_limit = inverter.current_charge_limit self.charge_window = inverter.charge_window self.export_window = inverter.export_window self.export_limits = inverter.export_limits @@ -593,6 +595,7 @@ def fetch_inverter_data(self): self.set_reserve_enable = False self.set_reserve_hold = False self.set_discharge_during_charge = True + self.current_charge_limit_kwh += dp2(inverter.current_charge_limit * inverter.soc_max / 100.0) self.soc_max += inverter.soc_max self.soc_kw += inverter.soc_kw self.reserve += inverter.reserve @@ -609,6 +612,7 @@ def fetch_inverter_data(self): self.export_limit += inverter.export_limit self.pv_power += inverter.pv_power self.load_power += inverter.load_power + self.current_charge_limit = calc_percent_limit(self.current_charge_limit_kwh, self.soc_max) # Remove extra decimals self.soc_max = dp2(self.soc_max) diff --git a/apps/predbat/plan.py b/apps/predbat/plan.py index b5791aa5..a3667dbe 100644 --- a/apps/predbat/plan.py +++ b/apps/predbat/plan.py @@ -1155,11 +1155,8 @@ def optimise_charge_limit(self, window_n, record_charge_windows, charge_limit, c # Metric adjustment based on current charge limit when inside the window # to try to avoid constant small changes to SoC target by forcing to keep the current % during a charge period # if changing it has little impact - if not all_n and self.isCharging and (window_n == self.in_charge_window(charge_window, self.minutes_now)) and (try_soc != self.reserve): - try_percent = calc_percent_limit(try_soc, self.soc_max) - compare_with = max(self.current_charge_limit, self.reserve_percent) - - if compare_with == try_percent: + if not all_n and self.isCharging and (window_n == self.in_charge_window(charge_window, self.minutes_now)): + if self.isCharging_Target == calc_percent_limit(try_soc, self.soc_max): metric -= max(0.1, self.metric_min_improvement) if try_soc == best_soc_min_setting: diff --git a/apps/predbat/predbat.py b/apps/predbat/predbat.py index f970005b..62129dc8 100644 --- a/apps/predbat/predbat.py +++ b/apps/predbat/predbat.py @@ -38,7 +38,7 @@ import asyncio import json -THIS_VERSION = "v8.8.16" +THIS_VERSION = "v8.8.17" # fmt: off PREDBAT_FILES = ["predbat.py", "config.py", "prediction.py", "gecloud.py","utils.py", "inverter.py", "ha.py", "download.py", "unit_test.py", "web.py", "predheat.py", "futurerate.py", "octopus.py", "solcast.py","execute.py", "plan.py", "fetch.py", "output.py", "userinterface.py"] @@ -438,7 +438,9 @@ def reset(self): self.computed_charge_curve = False self.computed_discharge_curve = False self.isCharging = False + self.isCharging_Target = 0 self.isExporting = False + self.isExporting_Target = 0 self.savings_today_predbat = 0.0 self.savings_today_predbat_soc = 0.0 self.savings_today_pvbat = 0.0 diff --git a/apps/predbat/unit_test.py b/apps/predbat/unit_test.py index 4738f115..37c67a72 100644 --- a/apps/predbat/unit_test.py +++ b/apps/predbat/unit_test.py @@ -998,7 +998,6 @@ def call_service_template(self, service, data, domain="charge", extra_data={}) ha.service_store_enable = False return failed - def run_car_charging_smart_test(test_name, my_predbat, battery_size=10.0, limit=8.0, soc=0, rate=10.0, loss=1.0, max_price=99, smart=True, plan_time="00:00:00", expect_cost=0, expect_kwh=0): """ Run a car charging smart test @@ -1037,10 +1036,9 @@ def run_car_charging_smart_test(test_name, my_predbat, battery_size=10.0, limit= print("ERROR: Car charging total cost should be {} got {}".format(expect_cost, total_cost)) failed = True print(slots) - + return failed - def run_car_charging_smart_tests(my_predbat): """ Test car charging smart @@ -1058,14 +1056,13 @@ def run_car_charging_smart_tests(my_predbat): failed |= run_car_charging_smart_test("smart2", my_predbat, battery_size=12.0, limit=10.0, soc=0, rate=10.0, loss=1.0, max_price=99, smart=False, expect_cost=150, expect_kwh=10) failed |= run_car_charging_smart_test("smart3", my_predbat, battery_size=12.0, limit=10.0, soc=2, rate=10.0, loss=1.0, max_price=99, smart=True, expect_cost=80, expect_kwh=8) failed |= run_car_charging_smart_test("smart4", my_predbat, battery_size=12.0, limit=10.0, soc=2, rate=10.0, loss=0.5, max_price=99, smart=True, expect_cost=160, expect_kwh=16) - failed |= run_car_charging_smart_test("smart5", my_predbat, battery_size=100.0, limit=100.0, soc=0, rate=1.0, loss=1, max_price=99, smart=True, expect_cost=12 * 15, expect_kwh=12, plan_time="00:00:00") - failed |= run_car_charging_smart_test("smart6", my_predbat, battery_size=100.0, limit=100.0, soc=0, rate=1.0, loss=1, max_price=99, smart=True, expect_cost=14 * 15, expect_kwh=14, plan_time="02:00:00") - failed |= run_car_charging_smart_test("smart7", my_predbat, battery_size=100.0, limit=100.0, soc=0, rate=1.0, loss=1, max_price=10, smart=True, expect_cost=7 * 10, expect_kwh=7, plan_time="02:00:00") - failed |= run_car_charging_smart_test("smart8", my_predbat, battery_size=100.0, limit=100.0, soc=0, rate=1.0, loss=1, max_price=10, smart=False, expect_cost=7 * 10, expect_kwh=7, plan_time="02:00:00") + failed |= run_car_charging_smart_test("smart5", my_predbat, battery_size=100.0, limit=100.0, soc=0, rate=1.0, loss=1, max_price=99, smart=True, expect_cost=12*15, expect_kwh=12, plan_time="00:00:00") + failed |= run_car_charging_smart_test("smart6", my_predbat, battery_size=100.0, limit=100.0, soc=0, rate=1.0, loss=1, max_price=99, smart=True, expect_cost=14*15, expect_kwh=14, plan_time="02:00:00") + failed |= run_car_charging_smart_test("smart7", my_predbat, battery_size=100.0, limit=100.0, soc=0, rate=1.0, loss=1, max_price=10, smart=True, expect_cost=7*10, expect_kwh=7, plan_time="02:00:00") + failed |= run_car_charging_smart_test("smart8", my_predbat, battery_size=100.0, limit=100.0, soc=0, rate=1.0, loss=1, max_price=10, smart=False, expect_cost=7*10, expect_kwh=7, plan_time="02:00:00") return failed - def run_inverter_tests(): """ Test the inverter functions @@ -1149,7 +1146,7 @@ def run_inverter_tests(): expect_pv_power=1.5, expect_load_power=2.5, expect_soc_kwh=6.6, - ) + ) my_predbat.args["givtcp_rest"] = None dummy_rest = DummyRestAPI() @@ -1559,7 +1556,7 @@ def adjust_charge_window(self, charge_start_time, charge_end_time, minutes_now): self.charge_start_time_minutes = (charge_start_time - self.midnight_utc).total_seconds() / 60 self.charge_end_time_minutes = (charge_end_time - self.midnight_utc).total_seconds() / 60 self.charge_time_enable = True - # print("Charge start_time {} charge_end_time {}".format(self.charge_start_time_minutes, self.charge_end_time_minutes)) + #print("Charge start_time {} charge_end_time {}".format(self.charge_start_time_minutes, self.charge_end_time_minutes)) def adjust_charge_immediate(self, target_soc, freeze=False): self.immediate_charge_soc_target = target_soc @@ -1577,7 +1574,7 @@ def adjust_force_export(self, force_export, new_start_time=None, new_end_time=No if new_end_time is not None: delta = new_end_time - self.midnight_utc self.discharge_end_time_minutes = delta.total_seconds() / 60 - # print("Force export {} start_time {} end_time {}".format(self.force_export, self.discharge_start_time_minutes, self.discharge_end_time_minutes)) + #print("Force export {} start_time {} end_time {}".format(self.force_export, self.discharge_start_time_minutes, self.discharge_end_time_minutes)) def adjust_idle_time(self, charge_start=None, charge_end=None, discharge_start=None, discharge_end=None): self.idle_charge_start = charge_start @@ -1617,6 +1614,7 @@ def run_execute_test( export_limits_best=[], car_slot=[], soc_kw=0, + soc_max=10, read_only=False, set_soc_enable=True, set_charge_window=False, @@ -1646,12 +1644,13 @@ def run_execute_test( has_charge_enable_time=True, inverter_hybrid=False, battery_max_rate=1000, - minutes_now=12 * 60, + minutes_now = 12 * 60, update_plan=False, ): print("Run scenario {}".format(name)) + failed = False my_predbat.soc_kw = soc_kw - my_predbat.soc_max = 10.0 + my_predbat.soc_max = soc_max my_predbat.reserve = 1 my_predbat.soc_percent = calc_percent_limit(soc_kw, my_predbat.soc_max) my_predbat.set_read_only = read_only @@ -1680,7 +1679,7 @@ def run_execute_test( for inverter in my_predbat.inverters: inverter.charge_start_time_minutes = inverter_charge_time_minutes_start inverter.soc_kw = soc_kw / total_inverters - inverter.soc_max = my_predbat.soc_max / total_inverters + inverter.soc_max = soc_max / total_inverters inverter.soc_percent = calc_percent_limit(inverter.soc_kw, inverter.soc_max) inverter.in_calibration = in_calibration inverter.battery_rate_max_charge = my_predbat.battery_rate_max_charge / total_inverters @@ -1689,7 +1688,17 @@ def run_execute_test( inverter.inv_has_target_soc = has_target_soc inverter.inv_has_charge_enable_time = has_charge_enable_time - failed = False + #my_predbat.fetch_inverter_data() + if my_predbat.soc_kw != soc_kw: + print("ERROR: Predat level SOC should be {} got {}".format(soc_kw, my_predbat.soc_kw)) + failed = True + if my_predbat.soc_percent != calc_percent_limit(my_predbat.soc_kw, my_predbat.soc_max): + print("ERROR: Predat level SOC percent should be {} got {}".format(calc_percent_limit(my_predbat.soc_kw, my_predbat.soc_max), my_predbat.soc_percent)) + failed = True + if my_predbat.soc_max != soc_max: + print("ERROR: Predat level SOC max should be {} got {}".format(soc_max, my_predbat.soc_max)) + failed = True + my_predbat.charge_window_best = charge_window_best my_predbat.charge_limit_best = charge_limit_best my_predbat.charge_limit_percent_best = [calc_percent_limit(x, my_predbat.soc_max) for x in charge_limit_best] @@ -1707,11 +1716,9 @@ def run_execute_test( # Shift on plan? if update_plan: my_predbat.plan_last_updated = my_predbat.now_utc - my_predbat.args["threads"] = 0 + my_predbat.args['threads'] = 0 my_predbat.calculate_plan(recompute=False) - print("charge_window_best {} charge_limit_best {} export_window_best {} export_limits_best {}".format(charge_window_best, charge_limit_best, export_window_best, export_limits_best)) - status, status_extra = my_predbat.execute_plan() for inverter in my_predbat.inverters: @@ -1798,9 +1805,9 @@ def run_single_debug(test_name, my_predbat, debug_file, expected_file=None): print("Combined export slots {} min_improvement_export {} set_export_freeze_only {}".format(my_predbat.combine_export_slots, my_predbat.metric_min_improvement_export, my_predbat.set_export_freeze_only)) if not expected_file: pass - # my_predbat.combine_export_slots = False + #my_predbat.combine_export_slots = False # my_predbat.best_soc_keep = 1.0 - # my_predbat.metric_min_improvement_export = 5 + #my_predbat.metric_min_improvement_export = 5 if re_do_rates: # Set rate thresholds @@ -1870,7 +1877,7 @@ def run_single_debug(test_name, my_predbat, debug_file, expected_file=None): my_predbat.charge_limit_percent_best = calc_percent_limit(my_predbat.charge_limit_best, my_predbat.soc_max) my_predbat.update_target_values() my_predbat.publish_html_plan(pv_step, pv10_step, load_step, load10_step, my_predbat.end_record) - filename = test_name + ".plan_orig.html" + filename = "plan_orig.html" open(filename, "w").write(my_predbat.html_plan) print("Wrote plan to {}".format(filename)) @@ -1885,12 +1892,17 @@ def run_single_debug(test_name, my_predbat, debug_file, expected_file=None): my_predbat.log("Final plan soc_min {} final_soc {}".format(soc_min, soc)) my_predbat.publish_html_plan(pv_step, pv10_step, load_step, load10_step, my_predbat.end_record) - filename = test_name + ".plan_final.html" + filename = "plan_final.html" open(filename, "w").write(my_predbat.html_plan) print("Wrote plan to {}".format(filename)) # Expected - actual_data = {"charge_limit_best": my_predbat.charge_limit_best, "charge_window_best": my_predbat.charge_window_best, "export_window_best": my_predbat.export_window_best, "export_limits_best": my_predbat.export_limits_best} + actual_data = { + "charge_limit_best": my_predbat.charge_limit_best, + "charge_window_best": my_predbat.charge_window_best, + "export_window_best": my_predbat.export_window_best, + "export_limits_best": my_predbat.export_limits_best + } actual_json = json.dumps(actual_data) if expected_file: print("Compare with {}".format(expected_file)) @@ -1909,7 +1921,6 @@ def run_single_debug(test_name, my_predbat, debug_file, expected_file=None): print("Wrote plan json to {}".format(filename)) return failed - def run_execute_tests(my_predbat): print("**** Running execute tests ****\n") reset_inverter(my_predbat) @@ -2958,7 +2969,7 @@ def run_execute_tests(my_predbat): assert_immediate_soc_target=0, assert_discharge_start_time_minutes=my_predbat.minutes_now, assert_discharge_end_time_minutes=my_predbat.minutes_now + 60 + 1, - minutes_now=775, + minutes_now = 775, ) if failed: return failed @@ -2979,7 +2990,7 @@ def run_execute_tests(my_predbat): assert_charge_start_time_minutes=-1, assert_charge_end_time_minutes=my_predbat.minutes_now + 90, assert_charge_time_enable=True, - minutes_now=780, + minutes_now = 780, update_plan=True, ) if failed: