Skip to content

Commit

Permalink
Fix service crash
Browse files Browse the repository at this point in the history
  • Loading branch information
springfall2008 authored Dec 27, 2024
1 parent d4a669b commit d4d0999
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 19 deletions.
2 changes: 1 addition & 1 deletion apps/predbat/inverter.py
Original file line number Diff line number Diff line change
Expand Up @@ -1933,7 +1933,7 @@ def call_service_template(self, service, data, domain="charge", extra_data={}):
else:
# Record the last service called
self.base.last_service_hash[hash_index] = this_service_hash
self.log("Inverter {} Calling service {} domain {} with data {}".format(self.id, service, domain, data))
self.log("Inverter {} Calling service {} domain {} with data {} extra_data {}".format(self.id, service, domain, data, extra_data))

if not isinstance(service_list, list):
service_list = [service_list]
Expand Down
124 changes: 107 additions & 17 deletions apps/predbat/unit_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def get_history(self, entity_id, now=None, days=30):

class TestInverter:
def __init__(self):
self.id = 0
pass


Expand Down Expand Up @@ -997,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 All @@ -1017,24 +1017,28 @@ def run_car_charging_smart_test(test_name, my_predbat, battery_size=10.0, limit=
my_predbat.car_charging_plan_time = [plan_time]
my_predbat.num_cars = 1

slots = my_predbat.plan_car_charging(0, my_predbat.low_rates)
my_predbat.car_charging_slots[0] = my_predbat.plan_car_charging(0, my_predbat.low_rates)
total_kwh = 0
total_cost = 0
for slot in slots:
for slot in my_predbat.car_charging_slots[0]:
total_kwh += slot["kwh"]
total_cost += slot["cost"]
if total_kwh != expect_kwh:
print("ERROR: Car charging total kwh should be {} got {}".format(expect_kwh, total_kwh))
failed = True
print(slots)
total_pd = my_predbat.car_charge_slot_kwh(my_predbat.minutes_now, my_predbat.minutes_now + my_predbat.forecast_minutes)
if total_pd != expect_kwh:
print("ERROR: Car charging total calculated with car_charge_slot_kwh should be {} got {}".format(expect_kwh, total_pd))
failed = True
print(slots)
if total_cost != expect_cost:
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 @@ -1052,13 +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("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 @@ -1187,6 +1191,8 @@ def run_inverter_tests():
failed |= test_call_service_template("test_service_simple4", my_predbat, inv, service_name="test_service", domain="charge", data={"test": "data"}, extra_data={"extra": "data"}, clear=False, repeat=True)
failed |= test_call_service_template("test_service_simple5", my_predbat, inv, service_name="test_service", domain="charge", data={"test": "data"}, extra_data={"extra": "data2"}, clear=False, repeat=False)

failed |= test_call_service_template("test_service_simple6", my_predbat, inv, service_name="test_service", domain="charge", data={"test": "data"}, extra_data={"extra_dummy": "data2"}, clear=False, repeat=False)

failed |= test_call_service_template(
"test_service_complex1",
my_predbat,
Expand All @@ -1202,7 +1208,23 @@ def run_inverter_tests():
failed |= test_call_service_template(
"test_service_complex2", my_predbat, inv, service_name="complex_service", domain="charge", data={"test": "data"}, extra_data={"extra": "extra_data"}, service_template={"service": "funny", "dummy": "22", "extra": "{extra}"}, clear=False, repeat=True
)

my_predbat.args["extra"] = "42"
failed |= test_call_service_template(
"test_service_complex3",
my_predbat,
inv,
service_name="complex_service",
domain="charge",
data={"test": "data"},
extra_data={"extra": "extra_data"},
service_template={"service": "funny", "dummy": "22", "extra": "{extra}"},
expected_result=[["funny", {"dummy": "22", "extra": "extra_data"}]],
clear=True,
)

inv.soc_percent = 49

failed |= test_call_adjust_charge_immediate("charge_immediate1", my_predbat, ha, inv, dummy_items, 100, clear=True, stop_discharge=True)
failed |= test_call_adjust_charge_immediate("charge_immediate2", my_predbat, ha, inv, dummy_items, 0)
failed |= test_call_adjust_charge_immediate("charge_immediate3", my_predbat, ha, inv, dummy_items, 0, repeat=True)
Expand Down Expand Up @@ -1534,6 +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))

def adjust_charge_immediate(self, target_soc, freeze=False):
self.immediate_charge_soc_target = target_soc
Expand All @@ -1551,6 +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))

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 @@ -1619,6 +1643,8 @@ def run_execute_test(
has_charge_enable_time=True,
inverter_hybrid=False,
battery_max_rate=1000,
minutes_now = 12 * 60,
update_plan=False,
):
print("Run scenario {}".format(name))
my_predbat.soc_kw = soc_kw
Expand All @@ -1631,6 +1657,12 @@ def run_execute_test(
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
my_predbat.minutes_now = minutes_now

charge_window_best = charge_window_best.copy()
charge_limit_best = charge_limit_best.copy()
export_window_best = export_window_best.copy()
export_limits_best = export_limits_best.copy()

if assert_immediate_soc_target is None:
assert_immediate_soc_target = assert_soc_target
Expand Down Expand Up @@ -1669,6 +1701,14 @@ def run_execute_test(
my_predbat.set_discharge_during_charge = set_discharge_during_charge
my_predbat.car_charging_from_battery = False

# Shift on plan?
if update_plan:
my_predbat.plan_last_updated = my_predbat.now_utc
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 @@ -1731,6 +1771,7 @@ def run_execute_test(
print("ERROR: Inverter {} Immediate export SOC freeze should be True got {}".format(inverter.id, inverter.immediate_discharge_soc_freeze))
failed = True

my_predbat.minutes_now = 12 * 60
return failed


Expand All @@ -1754,9 +1795,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 @@ -1846,7 +1887,12 @@ def run_single_debug(test_name, my_predbat, debug_file, expected_file=None):
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 @@ -1865,7 +1911,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 All @@ -1880,6 +1925,7 @@ def run_execute_tests(my_predbat):
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_best0 = [10]
charge_limit_best = [10, 10]
charge_limit_best2 = [5]
charge_limit_best_frz = [1]
Expand All @@ -1905,6 +1951,7 @@ def run_execute_tests(my_predbat):

failed |= run_execute_test(my_predbat, "no_charge", charge_window_best=charge_window_best, charge_limit_best=charge_limit_best)
failed |= run_execute_test(my_predbat, "no_charge2", set_charge_window=True, set_export_window=True, set_discharge_during_charge=False)
failed |= run_execute_test(my_predbat, "no_charge3", set_charge_window=True, set_export_window=True, set_discharge_during_charge=False, has_timed_pause=False)
failed |= run_execute_test(my_predbat, "no_charge_future", set_charge_window=True, set_export_window=True, charge_window_best=charge_window_best4, charge_limit_best=charge_limit_best)
failed |= run_execute_test(my_predbat, "no_charge_future_hybrid", set_charge_window=True, set_export_window=True, charge_window_best=charge_window_best4, charge_limit_best=charge_limit_best, inverter_hybrid=True)
failed |= run_execute_test(
Expand Down Expand Up @@ -2295,7 +2342,7 @@ def run_execute_tests(my_predbat):
)
failed |= run_execute_test(
my_predbat,
"charge23",
"charge2d",
charge_window_best=charge_window_best,
charge_limit_best=charge_limit_best2,
assert_charge_time_enable=True,
Expand All @@ -2307,6 +2354,26 @@ def run_execute_tests(my_predbat):
assert_charge_end_time_minutes=my_predbat.minutes_now + 60,
assert_soc_target=50,
)
failed |= run_execute_test(
my_predbat,
"charge2e",
charge_window_best=charge_window_best,
charge_limit_best=charge_limit_best2,
set_charge_window=True,
set_export_window=True,
assert_status="Charging",
soc_kw=0,
assert_charge_start_time_minutes=-1,
assert_charge_end_time_minutes=my_predbat.minutes_now + 60,
assert_discharge_rate=0,
assert_pause_discharge=False,
assert_reserve=0,
assert_immediate_soc_target=50,
assert_charge_time_enable=True,
assert_soc_target=50,
has_timed_pause=False,
set_discharge_during_charge=False,
)
failed |= run_execute_test(
my_predbat,
"charge3",
Expand Down Expand Up @@ -2879,19 +2946,42 @@ def run_execute_tests(my_predbat):

failed |= run_execute_test(
my_predbat,
"discharge_charge",
"discharge_charge1",
export_window_best=export_window_best,
export_limits_best=export_limits_best,
charge_limit_best=charge_limit_best,
charge_limit_best=charge_limit_best0,
charge_window_best=charge_window_best9,
assert_force_export=True,
set_charge_window=True,
set_export_window=True,
soc_kw=10,
soc_kw=9,
assert_status="Exporting",
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,
)
if failed:
return failed

failed |= run_execute_test(
my_predbat,
"discharge_charge2",
export_window_best=export_window_best,
export_limits_best=export_limits_best,
charge_limit_best=charge_limit_best0,
charge_window_best=charge_window_best9,
assert_force_export=False,
set_charge_window=True,
set_export_window=True,
soc_kw=9,
assert_status="Charging",
assert_immediate_soc_target=100,
assert_charge_start_time_minutes=-1,
assert_charge_end_time_minutes=my_predbat.minutes_now + 90,
assert_charge_time_enable=True,
minutes_now = 780,
update_plan=True,
)
if failed:
return failed
Expand Down
6 changes: 5 additions & 1 deletion apps/predbat/userinterface.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ def resolve_arg(self, arg, value, default=None, indirect=True, combine=False, at
if isinstance(value, str) and "{" in value:
try:
if extra_args:
value = value.format(**self.args, **extra_args)
# Remove duplicates or format will fail
arg_hash = {}
arg_hash.update(self.args)
arg_hash.update(extra_args)
value = value.format(**arg_hash)
else:
value = value.format(**self.args)
except KeyError:
Expand Down

0 comments on commit d4d0999

Please sign in to comment.