Skip to content

Commit

Permalink
Attempt to fix unstable plan
Browse files Browse the repository at this point in the history
  • Loading branch information
springfall2008 authored Dec 27, 2024
1 parent 9a56d12 commit 14f55a3
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 34 deletions.
8 changes: 6 additions & 2 deletions apps/predbat/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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])
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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)
Expand Down
7 changes: 2 additions & 5 deletions apps/predbat/plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
4 changes: 3 additions & 1 deletion apps/predbat/predbat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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
Expand Down
63 changes: 37 additions & 26 deletions apps/predbat/unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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]
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))

Expand All @@ -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))
Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand All @@ -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:
Expand Down

0 comments on commit 14f55a3

Please sign in to comment.